Code Monkey home page Code Monkey logo

snoop's Introduction

snoop

Build Status Supports Python versions 2.7 and 3.5+, including PyPy

snoop is a powerful set of Python debugging tools. It's primarily meant to be a more featureful and refined version of PySnooper. It also includes its own version of icecream and some other nifty stuff.

You're trying to figure out why your Python code isn't doing what you think it should be doing. You'd love to use a full-fledged debugger with breakpoints and watches, but you can't be bothered to set one up right now.

You want to know which lines are running and which aren't, and what the values of the local variables are.

Most people would use print lines, in strategic locations, some of them showing the values of variables.

snoop lets you do the same, except instead of carefully crafting the right print lines, you just add one decorator line to the function you're interested in. You'll get a play-by-play log of your function, including which lines ran and when, and exactly when local variables were changed.

Installation is as simple as pip install snoop.

You can try it out instantly on futurecoder: enter your code in the editor on the left and click the snoop button to run. No imports or decorators required.

Basic snoop usage

We're writing a function that converts a number to binary, by returning a list of bits. Let's snoop on it by adding the @snoop decorator:

import snoop

@snoop
def number_to_bits(number):
    if number:
        bits = []
        while number:
            number, remainder = divmod(number, 2)
            bits.insert(0, remainder)
        return bits
    else:
        return [0]

number_to_bits(6)

Note how easy it is: Just import snoop and @snoop. If you don't like the magical import, snoop.snoop and from snoop import snoop still work too. Or if you don't want to import in your project at all, just call install() somewhere once.

The output to stderr looks like this:

number_to_bits output

Let's try a more complex example. We're writing a memoizing decorator: it stores function arguments and return values in a cache to avoid recomputation:

import snoop

def cache(func):
    d = {}

    def wrapper(*args):
        try:
            return d[args]
        except KeyError:
            result = d[args] = func(*args)
            return result

    return wrapper

@snoop(depth=2)
@cache
def add(x, y):
    return x + y

add(1, 2)
add(1, 2)

Here we specify depth=2 to mean we should also step one level down into inner function calls. We then call the function twice to see the caching in action. Here's the output:

cache output

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:

import snoop
import random

def foo():
    lst = []
    for i in range(10):
        lst.append(random.randrange(1, 1000))

    with snoop:
        lower = min(lst)
        upper = max(lst)
        mid = (lower + upper) / 2

    return lower, mid, upper

foo()

which outputs something like:

foo output

Common arguments

  • 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:
@snoop(watch=('foo.bar', 'self.x["whatever"]'))
  • watch_explode: Expand variables or expressions to see all their attributes or items of lists/dictionaries:
@snoop(watch_explode=['foo', 'self'])

This will output lines like:

........ foo[2] = 'whatever'
........ self.baz = 8

See Controlling watch_explode for more advanced usage of this argument.

See watch_extras to show additional information about any value (local variable, watched expression, or exploded item) automatically.

pp - awesome print debugging

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. If prettyprinter or 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.

Here's an example:

from snoop import pp
x = 1
y = 2
pp(pp(x + 1) + max(*pp(y + 2, y + 3)))

Output:

12:34:56.78 LOG:
12:34:56.78 .... x + 1 = 2
12:34:56.78 LOG:
12:34:56.78 .... y + 2 = 4
12:34:56.78 .... y + 3 = 5
12:34:56.78 LOG:
12:34:56.78 .... pp(x + 1) + max(*pp(y + 2, y + 3)) = 7

If you've already got import snoop you can also use snoop.pp. But ideally, you would use install() to avoid importing at all.

There are a few situations where pp can't find the source code of its arguments, in which case it will show a placeholder instead:

  • When the source file cannot be found, usually because it doesn't exist, e.g. if you're in a Python shell. The source is fetched from linecache.
  • 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 decorator).
  • When the source file has been modified before the first call to pp or snoop.

Under the hood, pp uses the library 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 and offers the same basic API for printing, but pp integrates seamlessly with snoop and offers pp.deep, which is unique.

'pp' stands for 'pretty-print' and totally definitely absolutely has no other meaning. It's also very easy and quick to type.

pp.deep for tracing subexpressions

If you have pp(<complicated expression>) and you want to see what happens inside that expression and not just the final value, replace it wth pp.deep(lambda: <complicated expression>). This will log every intermediate subexpression, in the correct order, with no additional side effects, and return the final value. Repeating the previous example:

pp.deep(lambda: x + 1 + max(y + 2, y + 3))

Output:

12:34:56.78 LOG:
12:34:56.78 ............ x = 1
12:34:56.78 ........ x + 1 = 2
12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 2 = 4
12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 3 = 5
12:34:56.78 ........ max(y + 2, y + 3) = 5
12:34:56.78 .... x + 1 + max(y + 2, y + 3) = 7

(the values of literals and builtins are left out because they're trivial)

If an exception is raised, it'll show which subexpression is responsible, which looks something like this:

12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 3 = 5
12:34:56.78 ........ (y + 3) / 0 = !!! ZeroDivisionError!
12:34:56.78 !!! ZeroDivisionError: division by zero

If you like this, you'll probably love @spy.

@spy

The @spy decorator lets you combine @snoop with the powerful debugger birdseye. The code:

from snoop import spy  # not required if you use install()

@spy
def foo():

is roughly equivalent to:

import snoop
from birdseye import eye

@snoop
@eye
def foo():

To reduce the dependencies of snoop, you'll need to install birdseye separately: pip install birdseye.

The only big disadvantage of @spy is that it significantly reduces performance, so avoid it for functions with many loop iterations. Otherwise you can basically always use it instead of @snoop. Then if the logs don't have the information you need you can open up the birdseye UI to see more detail, without needing to edit or rerun your code. Great for when you're feeling lazy and unsure which tool is best.

spy passes its arguments to snoop, so e.g. @spy(depth=2, watch='x.y') works.

Read more about birdseye in the documentation here.

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

install()

To make regularly debugging your project more convenient, run this code early on:

import snoop

snoop.install()

Then snoop, pp, and spy will be available in every file without needing to import them.

You can choose different names by passing keyword arguments <original name>=<new name>, e.g:

snoop.install(snoop="ss")

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.

Disabling

If you would like to leave snoop and other functions in your codebase but disable their effects, pass enabled=False. For example, if you're using Django, put snoop.install(enabled=DEBUG) in settings.py to automatically disable it in production. When disabled, performance impact is minimised and there is no output anywhere.

You can also dynamically re-enable the functions at any point by calling snoop.install(enabled=True) again, e.g. in a special view or signal handler.

Output configuration

install has several keyword arguments for controlling the output of snoop and pp:

  • out: determines the output destination. By default this is stderr. You can also pass:

    • A string or a Path object to write to a file at that location. By default this always will append to the file. Pass overwrite=True to clear the file initially.
    • 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, 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) 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_ident: The identifier 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

    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(), 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:

  • 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 using custom_repr, see watch_extras and Customising the display of variables.

If you're not sure if it's worth using snoop instead of PySnooper, read the comparison here.

IPython/Jupyter integration

snoop comes with an IPython extension that you can use in shells or notebooks.

First you need to load the extension, using either %load_ext snoop in a notebook cell or by adding 'snoop' to the list c.InteractiveShellApp.extensions in your IPython configuration file, e.g. ~/.ipython/profile_default/ipython_config.py.

Then use the cell magic %%snoop at the top of a notebook cell to trace that cell:

%%snoop example

Advanced usage

watch_extras

install has another parameter called watch_extras. You can pass it a list of functions to automatically show extra information about any value: local variables, watched expressions, and exploded items. For example, suppose you wanted to see the type of every variable. You could define a function like this:

def type_watch(source, value):
    return 'type({})'.format(source), type(value)

You would then write install(watch_extras=[type_watch]). The result is output like this:

12:34:56.78    9 |     x = 1
12:34:56.78 .......... type(x) = <class 'int'>
12:34:56.78   10 |     y = [x]
12:34:56.78 .......... y = [1]
12:34:56.78 .......... type(y) = <class 'list'>

The functions you write should accept two arguments source and value - typically these will be the name of a variable and its actual value. They should return a pair representing the 'source' of the returned information (used only for display, it doesn't have to be valid Python) and the actual information. If you don't want to display anything for this particular value, return None. Any exceptions raised are caught and silenced.

Two such functions are already enabled by default: one which shows either the len() or the .shape property (used by numpy, pandas, tensorflow, etc) of values, and one which shows the .dtype property.

watch_extras is added to these two default functions so you don't have to specify them again. If you don't want to include them, use replace_watch_extras instead to specify the exact list. The original functions can be found here:

from snoop.configuration import len_shape_watch, dtype_watch

Controlling watch_explode

watch_explode will automatically guess how to expand the expression passed to it based on its class. You can be more specific by using one of the following classes:

@snoop(watch=(
    snoop.Attrs('x'),    # Attributes (specifically from __dict__ or __slots__)
    snoop.Keys('y'),     # Mapping (e.g. dict) items, based on .keys()
    snoop.Indices('z'),  # Sequence (e.g. list/tuple) items, based on len()
))

Exclude specific keys/attributes/indices with the exclude parameter, e.g. Attrs('x', exclude=('_foo', '_bar')).

Add a slice after Indices to only see the values within that slice, e.g. Indices('z')[-3:].

Customising the display of variables

(See also watch_extras)

Values are rendered using the cheap_repr library to improve performance and avoid flooding the console. It has a specially defined repr function for most common classes, including from third party libraries. If a class is missing, please open an issue there. You can also register your own repr for the class. Here's an example:

from cheap_repr import register_repr, cheap_repr

@register_repr(MyClass)
def repr_my_class(x, helper):
    return '{}(items={})'.format(
        x.__class__.__name__,
        cheap_repr(x.items, helper.level - 1),
    )

Read more here.

You can also increase the verbosity of individual classes (see the documentation), e.g:

from cheap_repr import find_repr_function

find_repr_function(list).maxparts = 100

Multiple separate configurations

If you need more control than the global install function, e.g. if you want to write to several different files in one process, you can create a Config object, e.g: config = snoop.Config(out=filename). Then config.snoop, config.pp and config.spy will use that configuration rather than the global one.

The arguments are the same as the arguments of install() relating to output configuration and enabled.

Contribute

Feedback and discussions

I'd love to hear from users! Obviously open an issue if you have one, but also check out the issues with the 'discussion' label. There's still a lot more work that can be done and I really want people's opinions so that I can do it right.

You can also email me what you like or hate about snoop. Just knowing it's being used is helpful.

Developing

Pull requests are always welcome!

Please, write tests and run them with Tox.

Tox installs all dependencies automatically. You only need to install Tox itself:

$ pip install tox

If you want to run tests against all target Python versions use pyenv to install them. Otherwise, you can run only the ones you have already installed on your machine:

# run only some interpreters
$ tox -e py27,py36

Or just install project in developer mode with test dependencies:

$ pip install -e path/to/snoop[tests]

And run tests:

$ pytest

snoop's People

Contributors

4383 avatar alexbers avatar alexmojaki avatar andreasvc avatar bittner avatar butuzov avatar cool-rr avatar czietz avatar danr avatar darksunium avatar edwardbetts avatar gpshead avatar lleontop avatar marksmayo avatar pohmelie avatar shlomif avatar spamegg1 avatar tirkarthi avatar volpatto avatar wilfredinni avatar zzzeek 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

snoop's Issues

Filter variables to output when using `with snoop:` ?

I tried to run "with snoop" on a part of my code and it printed this in the console

I wish there's some way to filter the variables to output when using "with snoop:" so I can remove some unnecessary output such as "object variables" and "class instance"

This is the part code I run with "with snoop":

with snoop:
    for files in sourcefiles:
        for file_type in images:
            if files.endswith(file_type):
                path = os.path.join(dir, files)
                for element in result:
                    if " " in element:
                        for element in element.split(" "):
                            filter(element)
                    else:
                        filter(element)
                fil_result.append("\n\n")
                per_picture.append(fil_result)
                fil_result = []

Then this is the result to the console:

07:17:12.34 >>> Enter with block in <module> in File "D:\OCR\New folder\safe prototype gui.py", line 347
07:17:12.34 ...... __name__ = '__main__'
07:17:12.34 ...... __doc__ = None
07:17:12.34 ...... __package__ = None
07:17:12.34 ...... __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0x0000021346836CD0>
07:17:12.34 ...... __spec__ = None
07:17:12.34 ...... __annotations__ = {}
07:17:12.34 ...... __builtins__ = <module 'builtins' (built-in)>
07:17:12.34 ...... __file__ = 'D:\\OCR\\New folder\\safe prototype gui.py'
07:17:12.34 ...... __cached__ = None
07:17:12.34 ...... QApplication = <class 'PyQt5.QtWidgets.QApplication'>
07:17:12.34 ...... QWidget = <class 'PyQt5.QtWidgets.QWidget'>
07:17:12.34 ...... QFileDialog = <class 'PyQt5.QtWidgets.QFileDialog'>
07:17:12.34 ...... QLineEdit = <class 'PyQt5.QtWidgets.QLineEdit'>
07:17:12.34 ...... QComboBox = <class 'PyQt5.QtWidgets.QComboBox'>
07:17:12.34 ...... QTableWidgetSelectionRange = <class 'PyQt5.QtWidgets.QTableWidgetSelectionRange'>
07:17:12.34 ...... QColor = <class 'PyQt5.QtGui.QColor'>
07:17:12.34 ...... QFont = <class 'PyQt5.QtGui.QFont'>
07:17:12.34 ...... QStandardItemModel = <class 'PyQt5.QtGui.QStandardItemModel'>
07:17:12.34 ...... QStandardItem = <class 'PyQt5.QtGui.QStandardItem'>
07:17:12.34 ...... uic = <module 'PyQt5.uic' from 'C:\\Users\\Eliaz\\AppD...39\\lib\\site-packages\\PyQt5\\uic\\__init__.py'>
07:17:12.34 ...... QtWidgets = <module 'PyQt5.QtWidgets' from 'C:\\Users\\Eliaz...hon39\\lib\\site-packages\\PyQt5\\QtWidgets.pyd'>
07:17:12.34 ...... Qt = <class 'PyQt5.QtCore.Qt'>
07:17:12.34 ...... QSortFilterProxyModel = <class 'PyQt5.QtCore.QSortFilterProxyModel'>
07:17:12.34 ...... QtCore = <module 'PyQt5.QtCore' from 'C:\\Users\\Eliaz\\A...Python39\\lib\\site-packages\\PyQt5\\QtCore.pyd'>
07:17:12.34 ...... QtGui = <module 'PyQt5.QtGui' from 'C:\\Users\\Eliaz\\Ap...\Python39\\lib\\site-packages\\PyQt5\\QtGui.pyd'>
07:17:12.34 ...... easyocr = <module 'easyocr' from 'C:\\Users\\Eliaz\\AppDat...hon39\\lib\\site-packages\\easyocr\\__init__.py'>
07:17:12.34 ...... sys = <module 'sys' (built-in)>
07:17:12.34 ...... os = <module 'os' from 'C:\\Users\\Eliaz\\AppData\\Local\\Programs\\Python\\Python39\\lib\\os.py'>
07:17:12.34 ...... ic = <icecream.icecream.IceCreamDebugger object at 0x000002135FCC2940>
07:17:12.34 ...... snoop = <class 'snoop.configuration.Config.__init__.<locals>.ConfiguredTracer'>
07:17:12.34 ...... ps = <module 'pysnooper' from 'C:\\Users\\Eliaz\\AppD...n39\\lib\\site-packages\\pysnooper\\__init__.py'>
07:17:12.34 ...... DialogApp = <class '__main__.DialogApp'>
07:17:12.34 ...... MyApp = <class '__main__.MyApp'>
07:17:12.34 ...... filter = <function filter at 0x000002134687DF70>
07:17:12.34 ...... MyAppGui = <function MyAppGui at 0x000002135FE6EF70>
07:17:12.34 ...... app = <PyQt5.QtWidgets.QApplication object at 0x000002135FE6F040>
07:17:12.34 ...... demo = <__main__.DialogApp object at 0x000002135FE6F0D0>
07:17:12.34 ...... replaces = {'k': 'rep', 'as': 'like'}
07:17:12.34 ...... len(replaces) = 2
07:17:12.34 ...... rep = <_io.TextIOWrapper name='D:\\OCR\\dict.txt' mode='r' encoding='cp1252'>
07:17:12.34 ...... line = 'earth\n'
07:17:12.34 ...... key = 'as'
07:17:12.34 ...... value = 'like'
07:17:12.34 ...... ignores = {'earth', 'lithosphere', 'The', 'and', 'properties', 'Layers', 'that', 'a', 'categorized', 'temperature,', ...}
07:17:12.34 ...... len(ignores) = 23
07:17:12.34 ...... ign = <_io.TextIOWrapper name='D:\\OCR\\ignores.txt' mode='r' encoding='cp1252'>
07:17:12.34 ...... val = 'earth'
07:17:12.34 ...... textfile = <_io.TextIOWrapper name='D:\\OCR\\ignore.txt' mode='w' encoding='cp1252'>
07:17:12.34 ...... images = ['.tiff', '.gif', '.png', '.eps', '.bmp', '.jpg', '.jpf', '.jpeg', '.ppm']
07:17:12.34 ...... len(images) = 9
07:17:12.34 ...... delete = set()
07:17:12.34 ...... unknown_words = set()
07:17:12.34 ...... fil_result = []
07:17:12.34 ...... result = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', ..., 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
07:17:12.34 ...... len(result) = 26
07:17:12.34 ...... list_of_strings_from_textedit = []
07:17:12.34 ...... per_picture = []
07:17:12.34 ...... dir = 'D:\\OCR\\test'
07:17:12.34 ...... sourcefiles = ['2021-09-15 08_06_48-(2) Facebook - Vivaldi.jpg', '2021-09-15 08_10_38-(2) Facebook - Vivaldi.jpg', 'textfile.txt']
07:17:12.34 ...... len(sourcefiles) = 3
07:17:12.34  348 |     for files in sourcefiles:
07:17:12.34 .......... files = '2021-09-15 08_06_48-(2) Facebook - Vivaldi.jpg'
07:17:12.34  349 |         for file_type in images:
07:17:12.34 .............. file_type = '.tiff'
07:17:12.34  350 |             if files.endswith(file_type):
07:17:12.34  349 |         for file_type in images:
07:17:12.34 .............. file_type = '.gif'
07:17:12.34  350 |             if files.endswith(file_type):
07:17:12.35  349 |         for file_type in images:
07:17:12.35 .............. file_type = '.png'
07:17:12.35  350 |             if files.endswith(file_type):
07:17:12.35  349 |         for file_type in images:
07:17:12.35 .............. file_type = '.eps'
07:17:12.35  350 |             if files.endswith(file_type):
07:17:12.35  349 |         for file_type in images:
07:17:12.35 .............. file_type = '.bmp'
07:17:12.35  350 |             if files.endswith(file_type):
07:17:12.35  349 |         for file_type in images:
07:17:12.35 .............. file_type = '.jpg'
07:17:12.35  350 |             if files.endswith(file_type):
07:17:12.35  351 |                 path = os.path.join(dir, files)
07:17:12.35 ...................... path = 'D:\\OCR\\test\\2021-09-15 08_06_48-(2) Facebook - Vivaldi.jpg'
07:17:12.35 ...................... len(path) = 58
07:17:12.35  355 |                 for element in result:
07:17:12.35 ...................... element = 'a'
07:17:12.35  356 |                     if " " in element:
07:17:12.35  360 |                         filter(element)
07:17:12.35 .............................. unknown_words = {'a'}
07:17:12.35 .............................. len(unknown_words) = 1
07:17:12.35 .............................. fil_result = ['a', ' ']
07:17:12.35 .............................. len(fil_result) = 2
07:17:12.35 .............................. __warningregistry__ = {'version': 22}
07:17:12.35 .............................. len(__warningregistry__) = 1
07:17:12.35  355 |                 for element in result:
07:17:12.35 ...................... element = 'b'
07:17:12.35  356 |                     if " " in element:
07:17:12.35  360 |                         filter(element)
07:17:12.35 .............................. unknown_words = {'b', 'a'}
07:17:12.35 .............................. len(unknown_words) = 2
07:17:12.35 .............................. fil_result = ['a', ' ', 'b', ' ']
07:17:12.35 .............................. len(fil_result) = 4
07:17:12.35  355 |                 for element in result:
07:17:12.35 ...................... element = 'c'
07:17:12.35  356 |                     if " " in element:
07:17:12.35  360 |                         filter(element)
07:17:12.35 .............................. unknown_words = {'b', 'a', 'c'}
07:17:12.35 .............................. len(unknown_words) = 3
07:17:12.35 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ']
07:17:12.35 .............................. len(fil_result) = 6
07:17:12.35  355 |                 for element in result:
07:17:12.36 ...................... element = 'd'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.36 .............................. unknown_words = {'b', 'a', 'c', 'd'}
07:17:12.36 .............................. len(unknown_words) = 4
07:17:12.36 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ']
07:17:12.36 .............................. len(fil_result) = 8
07:17:12.36  355 |                 for element in result:
07:17:12.36 ...................... element = 'e'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.36 .............................. unknown_words = {'b', 'c', 'd', 'a', 'e'}
07:17:12.36 .............................. len(unknown_words) = 5
07:17:12.36 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ']
07:17:12.36 .............................. len(fil_result) = 10
07:17:12.36  355 |                 for element in result:
07:17:12.36 ...................... element = 'f'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.36 .............................. unknown_words = {'b', 'c', 'd', 'a', 'f', 'e'}
07:17:12.36 .............................. len(unknown_words) = 6
07:17:12.36 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ']
07:17:12.36 .............................. len(fil_result) = 12
07:17:12.36  355 |                 for element in result:
07:17:12.36 ...................... element = 'g'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.36 .............................. unknown_words = {'b', 'c', 'd', 'a', 'g', 'f', 'e'}
07:17:12.36 .............................. len(unknown_words) = 7
07:17:12.36 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ']
07:17:12.36 .............................. len(fil_result) = 14
07:17:12.36  355 |                 for element in result:
07:17:12.36 ...................... element = 'h'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.36 .............................. unknown_words = {'b', 'c', 'd', 'a', 'g', 'f', 'h', 'e'}
07:17:12.36 .............................. len(unknown_words) = 8
07:17:12.36 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ', 'h', ' ']
07:17:12.36 .............................. len(fil_result) = 16
07:17:12.36  355 |                 for element in result:
07:17:12.36 ...................... element = 'i'
07:17:12.36  356 |                     if " " in element:
07:17:12.36  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'c', 'd', 'a', 'i', 'g', 'f', 'h', 'e'}
07:17:12.38 .............................. len(unknown_words) = 9
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ', 'h', ' ', 'i', ' ']
07:17:12.38 .............................. len(fil_result) = 18
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'j'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'c', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'e'}
07:17:12.38 .............................. len(unknown_words) = 10
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'g', ' ', 'h', ' ', 'i', ' ', 'j', ' ']
07:17:12.38 .............................. len(fil_result) = 20
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'k'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'k', 'c', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'e'}
07:17:12.38 .............................. len(unknown_words) = 11
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'h', ' ', 'i', ' ', 'j', ' ', 'k', ' ']
07:17:12.38 .............................. len(fil_result) = 22
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'l'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'k', 'c', 'l', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'e'}
07:17:12.38 .............................. len(unknown_words) = 12
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'i', ' ', 'j', ' ', 'k', ' ', 'l', ' ']
07:17:12.38 .............................. len(fil_result) = 24
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'm'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'k', 'c', 'l', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'm', 'e'}
07:17:12.38 .............................. len(unknown_words) = 13
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'j', ' ', 'k', ' ', 'l', ' ', 'm', ' ']
07:17:12.38 .............................. len(fil_result) = 26
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'n'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.38 .............................. unknown_words = {'b', 'k', 'c', 'l', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'n', 'm', 'e'}
07:17:12.38 .............................. len(unknown_words) = 14
07:17:12.38 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'k', ' ', 'l', ' ', 'm', ' ', 'n', ' ']
07:17:12.38 .............................. len(fil_result) = 28
07:17:12.38  355 |                 for element in result:
07:17:12.38 ...................... element = 'o'
07:17:12.38  356 |                     if " " in element:
07:17:12.38  360 |                         filter(element)
07:17:12.40 .............................. unknown_words = {'b', 'k', 'c', 'l', 'd', 'a', 'i', 'g', 'f', 'j', 'h', 'n', 'm', 'e', 'o'}
07:17:12.40 .............................. len(unknown_words) = 15
07:17:12.40 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'l', ' ', 'm', ' ', 'n', ' ', 'o', ' ']
07:17:12.40 .............................. len(fil_result) = 30
07:17:12.40  355 |                 for element in result:
07:17:12.40 ...................... element = 'p'
07:17:12.40  356 |                     if " " in element:
07:17:12.40  360 |                         filter(element)
07:17:12.40 .............................. unknown_words = {'b', 'k', 'c', 'l', 'd', 'p', 'a', 'i', 'g', 'f', 'j', 'h', 'n', 'm', 'e', 'o'}
07:17:12.40 .............................. len(unknown_words) = 16
07:17:12.40 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'm', ' ', 'n', ' ', 'o', ' ', 'p', ' ']
07:17:12.40 .............................. len(fil_result) = 32
07:17:12.40  355 |                 for element in result:
07:17:12.40 ...................... element = 'q'
07:17:12.40  356 |                     if " " in element:
07:17:12.40  360 |                         filter(element)
07:17:12.40 .............................. unknown_words = {'b', 'k', 'c', 'q', 'l', 'd', 'p', 'a', 'i', 'g', 'f', 'j', 'h', 'n', 'm', 'e', 'o'}
07:17:12.40 .............................. len(unknown_words) = 17
07:17:12.40 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'n', ' ', 'o', ' ', 'p', ' ', 'q', ' ']
07:17:12.40 .............................. len(fil_result) = 34
07:17:12.40  355 |                 for element in result:
07:17:12.40 ...................... element = 'r'
07:17:12.40  356 |                     if " " in element:
07:17:12.40  360 |                         filter(element)
07:17:12.40 .............................. unknown_words = {'b', 'k', 'c', 'q', 'l', 'd', 'r', 'p', 'a', 'i', 'g', 'f', 'j', 'h', 'n', 'm', 'e', 'o'}
07:17:12.40 .............................. len(unknown_words) = 18
07:17:12.40 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'o', ' ', 'p', ' ', 'q', ' ', 'r', ' ']
07:17:12.40 .............................. len(fil_result) = 36
07:17:12.40  355 |                 for element in result:
07:17:12.40 ...................... element = 's'
07:17:12.40  356 |                     if " " in element:
07:17:12.40  360 |                         filter(element)
07:17:12.41 .............................. unknown_words = {'b', 'k', 'd', 'g', 'f', 'j', 'n', 'r', 'p', 'a', 'e', 'c', 'l', 's', 'i', 'h', 'o', 'q', 'm'}
07:17:12.41 .............................. len(unknown_words) = 19
07:17:12.41 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'p', ' ', 'q', ' ', 'r', ' ', 's', ' ']
07:17:12.41 .............................. len(fil_result) = 38
07:17:12.41  355 |                 for element in result:
07:17:12.41 ...................... element = 't'
07:17:12.41  356 |                     if " " in element:
07:17:12.41  360 |                         filter(element)
07:17:12.41 .............................. unknown_words = {'b', 'k', 'd', 'g', 'f', 'j', 'n', 'r', 'p', 'a', 't', 'e', 'c', 'l', 's', 'i', 'h', 'o', 'q', ...}
07:17:12.41 .............................. len(unknown_words) = 20
07:17:12.41 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'q', ' ', 'r', ' ', 's', ' ', 't', ' ']
07:17:12.41 .............................. len(fil_result) = 40
07:17:12.41  355 |                 for element in result:
07:17:12.41 ...................... element = 'u'
07:17:12.41  356 |                     if " " in element:
07:17:12.41  360 |                         filter(element)
07:17:12.41 .............................. len(unknown_words) = 21
07:17:12.41 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'r', ' ', 's', ' ', 't', ' ', 'u', ' ']
07:17:12.41 .............................. len(fil_result) = 42
07:17:12.41  355 |                 for element in result:
07:17:12.41 ...................... element = 'v'
07:17:12.41  356 |                     if " " in element:
07:17:12.41  360 |                         filter(element)
07:17:12.41 .............................. unknown_words = {'b', 'k', 'd', 'g', 'f', 'j', 'n', 'v', 'r', 'p', 'a', 't', 'e', 'c', 'l', 's', 'i', 'h', 'o', ...}
07:17:12.41 .............................. len(unknown_words) = 22
07:17:12.41 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 's', ' ', 't', ' ', 'u', ' ', 'v', ' ']
07:17:12.41 .............................. len(fil_result) = 44
07:17:12.41  355 |                 for element in result:
07:17:12.42 ...................... element = 'w'
07:17:12.42  356 |                     if " " in element:
07:17:12.42  360 |                         filter(element)
07:17:12.42 .............................. unknown_words = {'b', 'k', 'd', 'w', 'g', 'f', 'j', 'n', 'v', 'r', 'p', 'a', 't', 'e', 'c', 'l', 's', 'i', 'h', ...}
07:17:12.42 .............................. len(unknown_words) = 23
07:17:12.42 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 't', ' ', 'u', ' ', 'v', ' ', 'w', ' ']
07:17:12.42 .............................. len(fil_result) = 46
07:17:12.42  355 |                 for element in result:
07:17:12.42 ...................... element = 'x'
07:17:12.42  356 |                     if " " in element:
07:17:12.42  360 |                         filter(element)
07:17:12.42 .............................. unknown_words = {'b', 'k', 'd', 'w', 'g', 'f', 'j', 'n', 'v', 'r', 'p', 'a', 'x', 't', 'e', 'c', 'l', 's', 'i', ...}
07:17:12.42 .............................. len(unknown_words) = 24
07:17:12.42 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'u', ' ', 'v', ' ', 'w', ' ', 'x', ' ']
07:17:12.42 .............................. len(fil_result) = 48
07:17:12.42  355 |                 for element in result:
07:17:12.42 ...................... element = 'y'
07:17:12.42  356 |                     if " " in element:
07:17:12.42  360 |                         filter(element)
07:17:12.42 .............................. unknown_words = {'b', 'k', 'd', 'w', 'g', 'f', 'j', 'n', 'y', 'v', 'r', 'p', 'a', 'x', 't', 'e', 'c', 'l', 's', ...}
07:17:12.42 .............................. len(unknown_words) = 25
07:17:12.42 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'v', ' ', 'w', ' ', 'x', ' ', 'y', ' ']
07:17:12.42 .............................. len(fil_result) = 50
07:17:12.42  355 |                 for element in result:
07:17:12.42 ...................... element = 'z'
07:17:12.42  356 |                     if " " in element:
07:17:12.42  360 |                         filter(element)
07:17:12.43 .............................. len(unknown_words) = 26
07:17:12.43 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'w', ' ', 'x', ' ', 'y', ' ', 'z', ' ']
07:17:12.43 .............................. len(fil_result) = 52
07:17:12.43  355 |                 for element in result:
07:17:12.43  362 |                 fil_result.append("\n\n")
07:17:12.43 ...................... fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., 'w', ' ', 'x', ' ', 'y', ' ', 'z', ' ', '\n\n']
07:17:12.43 ...................... len(fil_result) = 53
07:17:12.43  363 |                 per_picture.append(fil_result)
07:17:12.43 ...................... per_picture = [['a', ' ', 'b', ..., 'z', ' ', '\n\n']]
07:17:12.43 ...................... len(per_picture) = 1
07:17:12.43  364 |                 fil_result = []
07:17:12.43  349 |         for file_type in images:
07:17:12.43 .............. file_type = '.jpf'
07:17:12.43  350 |             if files.endswith(file_type):
07:17:12.43  349 |         for file_type in images:
07:17:12.43 .............. file_type = '.jpeg'
07:17:12.43  350 |             if files.endswith(file_type):
07:17:12.43  349 |         for file_type in images:
07:17:12.43 .............. file_type = '.ppm'
07:17:12.43  350 |             if files.endswith(file_type):
07:17:12.43  349 |         for file_type in images:
07:17:12.43  348 |     for files in sourcefiles:
07:17:12.44 .......... files = '2021-09-15 08_10_38-(2) Facebook - Vivaldi.jpg'
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.tiff'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.gif'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.png'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.eps'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.bmp'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.44  349 |         for file_type in images:
07:17:12.44 .............. file_type = '.jpg'
07:17:12.44  350 |             if files.endswith(file_type):
07:17:12.45  351 |                 path = os.path.join(dir, files)
07:17:12.45 ...................... path = 'D:\\OCR\\test\\2021-09-15 08_10_38-(2) Facebook - Vivaldi.jpg'
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'a'
07:17:12.45  356 |                     if " " in element:
07:17:12.45  360 |                         filter(element)
07:17:12.45 .............................. fil_result = ['a', ' ']
07:17:12.45 .............................. len(fil_result) = 2
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'b'
07:17:12.45  356 |                     if " " in element:
07:17:12.45  360 |                         filter(element)
07:17:12.45 .............................. fil_result = ['a', ' ', 'b', ' ']
07:17:12.45 .............................. len(fil_result) = 4
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'c'
07:17:12.45  356 |                     if " " in element:
07:17:12.45  360 |                         filter(element)
07:17:12.45 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ']
07:17:12.45 .............................. len(fil_result) = 6
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'd'
07:17:12.45  356 |                     if " " in element:
07:17:12.45  360 |                         filter(element)
07:17:12.45 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ']
07:17:12.45 .............................. len(fil_result) = 8
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'e'
07:17:12.45  356 |                     if " " in element:
07:17:12.45  360 |                         filter(element)
07:17:12.45 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ']
07:17:12.45 .............................. len(fil_result) = 10
07:17:12.45  355 |                 for element in result:
07:17:12.45 ...................... element = 'f'
07:17:12.45  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ']
07:17:12.46 .............................. len(fil_result) = 12
07:17:12.46  355 |                 for element in result:
07:17:12.46 ...................... element = 'g'
07:17:12.46  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ']
07:17:12.46 .............................. len(fil_result) = 14
07:17:12.46  355 |                 for element in result:
07:17:12.46 ...................... element = 'h'
07:17:12.46  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ', 'h', ' ']
07:17:12.46 .............................. len(fil_result) = 16
07:17:12.46  355 |                 for element in result:
07:17:12.46 ...................... element = 'i'
07:17:12.46  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', 'f', ' ', 'g', ' ', 'h', ' ', 'i', ' ']
07:17:12.46 .............................. len(fil_result) = 18
07:17:12.46  355 |                 for element in result:
07:17:12.46 ...................... element = 'j'
07:17:12.46  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'g', ' ', 'h', ' ', 'i', ' ', 'j', ' ']
07:17:12.46 .............................. len(fil_result) = 20
07:17:12.46  355 |                 for element in result:
07:17:12.46 ...................... element = 'k'
07:17:12.46  356 |                     if " " in element:
07:17:12.46  360 |                         filter(element)
07:17:12.46 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'h', ' ', 'i', ' ', 'j', ' ', 'k', ' ']
07:17:12.46 .............................. len(fil_result) = 22
07:17:12.46  355 |                 for element in result:
07:17:12.48 ...................... element = 'l'
07:17:12.48  356 |                     if " " in element:
07:17:12.48  360 |                         filter(element)
07:17:12.48 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'i', ' ', 'j', ' ', 'k', ' ', 'l', ' ']
07:17:12.48 .............................. len(fil_result) = 24
07:17:12.48  355 |                 for element in result:
07:17:12.48 ...................... element = 'm'
07:17:12.48  356 |                     if " " in element:
07:17:12.48  360 |                         filter(element)
07:17:12.48 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'j', ' ', 'k', ' ', 'l', ' ', 'm', ' ']
07:17:12.48 .............................. len(fil_result) = 26
07:17:12.48  355 |                 for element in result:
07:17:12.48 ...................... element = 'n'
07:17:12.48  356 |                     if " " in element:
07:17:12.48  360 |                         filter(element)
07:17:12.48 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'k', ' ', 'l', ' ', 'm', ' ', 'n', ' ']
07:17:12.48 .............................. len(fil_result) = 28
07:17:12.48  355 |                 for element in result:
07:17:12.48 ...................... element = 'o'
07:17:12.48  356 |                     if " " in element:
07:17:12.48  360 |                         filter(element)
07:17:12.49 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'l', ' ', 'm', ' ', 'n', ' ', 'o', ' ']
07:17:12.49 .............................. len(fil_result) = 30
07:17:12.49  355 |                 for element in result:
07:17:12.49 ...................... element = 'p'
07:17:12.49  356 |                     if " " in element:
07:17:12.50  360 |                         filter(element)
07:17:12.50 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'm', ' ', 'n', ' ', 'o', ' ', 'p', ' ']
07:17:12.50 .............................. len(fil_result) = 32
07:17:12.50  355 |                 for element in result:
07:17:12.50 ...................... element = 'q'
07:17:12.50  356 |                     if " " in element:
07:17:12.50  360 |                         filter(element)
07:17:12.50 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'n', ' ', 'o', ' ', 'p', ' ', 'q', ' ']
07:17:12.50 .............................. len(fil_result) = 34
07:17:12.50  355 |                 for element in result:
07:17:12.50 ...................... element = 'r'
07:17:12.50  356 |                     if " " in element:
07:17:12.50  360 |                         filter(element)
07:17:12.50 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'o', ' ', 'p', ' ', 'q', ' ', 'r', ' ']
07:17:12.50 .............................. len(fil_result) = 36
07:17:12.50  355 |                 for element in result:
07:17:12.50 ...................... element = 's'
07:17:12.50  356 |                     if " " in element:
07:17:12.50  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'p', ' ', 'q', ' ', 'r', ' ', 's', ' ']
07:17:12.51 .............................. len(fil_result) = 38
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 't'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'q', ' ', 'r', ' ', 's', ' ', 't', ' ']
07:17:12.51 .............................. len(fil_result) = 40
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'u'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'r', ' ', 's', ' ', 't', ' ', 'u', ' ']
07:17:12.51 .............................. len(fil_result) = 42
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'v'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 's', ' ', 't', ' ', 'u', ' ', 'v', ' ']
07:17:12.51 .............................. len(fil_result) = 44
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'w'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 't', ' ', 'u', ' ', 'v', ' ', 'w', ' ']
07:17:12.51 .............................. len(fil_result) = 46
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'x'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'u', ' ', 'v', ' ', 'w', ' ', 'x', ' ']
07:17:12.51 .............................. len(fil_result) = 48
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'y'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'v', ' ', 'w', ' ', 'x', ' ', 'y', ' ']
07:17:12.51 .............................. len(fil_result) = 50
07:17:12.51  355 |                 for element in result:
07:17:12.51 ...................... element = 'z'
07:17:12.51  356 |                     if " " in element:
07:17:12.51  360 |                         filter(element)
07:17:12.51 .............................. fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., ' ', 'w', ' ', 'x', ' ', 'y', ' ', 'z', ' ']
07:17:12.51 .............................. len(fil_result) = 52
07:17:12.51  355 |                 for element in result:
07:17:12.51  362 |                 fil_result.append("\n\n")
07:17:12.51 ...................... fil_result = ['a', ' ', 'b', ' ', 'c', ' ', 'd', ' ', 'e', ' ', ..., 'w', ' ', 'x', ' ', 'y', ' ', 'z', ' ', '\n\n']
07:17:12.51 ...................... len(fil_result) = 53
07:17:12.51  363 |                 per_picture.append(fil_result)
07:17:12.53 ...................... per_picture = [['a', ' ', 'b', ..., 'z', ' ', '\n\n'], ['a', ' ', 'b', ..., 'z', ' ', '\n\n']]
07:17:12.53 ...................... len(per_picture) = 2
07:17:12.53  364 |                 fil_result = []
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.jpf'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.jpeg'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.ppm'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53  348 |     for files in sourcefiles:
07:17:12.53 .......... files = 'textfile.txt'
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.tiff'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.gif'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.png'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.eps'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.53 .............. file_type = '.bmp'
07:17:12.53  350 |             if files.endswith(file_type):
07:17:12.53  349 |         for file_type in images:
07:17:12.54 .............. file_type = '.jpg'
07:17:12.54  350 |             if files.endswith(file_type):
07:17:12.54  349 |         for file_type in images:
07:17:12.54 .............. file_type = '.jpf'
07:17:12.54  350 |             if files.endswith(file_type):
07:17:12.54  349 |         for file_type in images:
07:17:12.54 .............. file_type = '.jpeg'
07:17:12.54  350 |             if files.endswith(file_type):
07:17:12.54  349 |         for file_type in images:
07:17:12.54 .............. file_type = '.ppm'
07:17:12.54  350 |             if files.endswith(file_type):
07:17:12.54  349 |         for file_type in images:
07:17:12.54  348 |     for files in sourcefiles:
07:17:12.54 <<< Exit with block in <module>
[Finished in 1.7s]

crash or line content mismatch

can't get it working with output to file.
It writes mismatched content for line number or crashes altogether (trying to output non existing lines)

image

Is there a limit to the depth?

When setting depth=20 on a script, snoop is failing with the following error.

'frame' object has no attribute 'lineno'

However, if the depth<=19, it works fine.

Use pprintpp if insalled

It would be nice if pprintpp would be used for pretty printing values if installed instead of build-in pprint. Or at least pretty printing function should be configurable.

pprintpp gives more readable output.

Decorating the entire class

In pysnooper I can add decorator before the class so that all methods are available for snooping. Doing the same with snoop gives TypeError: compile() arg 1 must be a string, bytes or AST object. I'm new with snooping, any help would be appreciated.

how to allow snoop for the entire file

I want any code run within the file to be snooped

SO how can i mention snoop at the beginning of the file

Currently i see example for a function decorator and also using with

Add logger support

Currently, snoop will dump all debug info into stdout, However, if there is program normal output via stdout. It would be too messy.

How about add option logger in the snoop() to dump all the debug information into the given logger.

multiple snoop configs display weirdly

Defining multiple snoops cause the console output stderr to dispaly two extra snoop outputs which are "Call to Tracer.call." and Return value from Tracer.call. which also contain extra long ".............." lines

snoop.install()
config = snoop.Config()

@snoop
@config.snoop
def get_repo_tree(user, repo, branch, logged_in_user=None):
  blah

I would expect two normal snoop outputs, instead I get one normal output wrapped in these tracer calls:

09:44:51.06 >>> Call to Tracer.__call__.<locals>.simple_wrapper in File "/Users/andy/.local/share/virtualenvs/gituml-Iv6Hkut1/lib/python3.6/site-packages/snoop/tracer.py", line 167
09:44:51.06 .............. args = ('matplotlib', 'matplotlib', '99d8900eacaad01b1ff6e5d482e4e17683dd36be')
09:44:51.06 .............. len(args) = 3
09:44:51.06 .............. kwargs = {'logged_in_user': <SimpleLazyObject: <User: andy22>>}
09:44:51.06 .............. len(kwargs) = 1
09:44:51.06 .............. function = <function get_repo_tree at 0x10d71b730>
09:44:51.06 .............. self = <snoop.configuration.Config.__init__.<locals>.ConfiguredTracer object at 0x10d6d8198>
09:44:51.06  167 |         def simple_wrapper(*args, **kwargs):
09:44:51.07  168 |             with self:
09:44:51.07  169 |                 return function(*args, **kwargs)

09:44:51.07 >>> Call to get_repo_tree in File "/Users/Andy/Devel/gituml/lib/repo/repo_tree.py", line 58
09:44:51.07 ...... user = 'matplotlib'
09:44:51.07 ...... repo = 'matplotlib'
09:44:51.07 ...... branch = '99d8900eacaad01b1ff6e5d482e4e17683dd36be'
09:44:51.07 ...... logged_in_user = <SimpleLazyObject: <User: andy22>>
09:44:51.07   58 | def get_repo_tree(user, repo, branch, logged_in_user=None):
09:44:51.07   73 |     if is_sha(branch):
09:44:51.07   74 |         return get_repo_tree_sha(user, repo, branch)
https://api.github.com/repos/matplotlib/matplotlib/git/trees/99d8900eacaad01b1ff6e5d482e4e17683dd36be?recursive=true
09:44:51.09 <<< Return value from get_repo_tree: ('{"sha":"99d8900eacaad01b1ff6e5d482e4e17683dd36be...1befdb06113c867356e4bea861e"}],"truncated":false}', '99d8900eacaad01b1ff6e5d482e4e17683dd36be')

09:44:51.10 <<< Return value from Tracer.__call__.<locals>.simple_wrapper: ('{"sha":"99d8900eacaad01b1ff6e5d482e4e17683dd36be...1befdb06113c867356e4bea861e"}],"truncated":false}', '99d8900eacaad01b1ff6e5d482e4e17683dd36be')
09:44:51.10  169 |                 return function(*args, **kwargs)
09:44:51.10 ...................... args = ('matplotlib', 'matplotlib', '99d8900eacaad01b1ff6e5d482e4e17683dd36be')
09:44:51.10 ...................... len(args) = 3
09:44:51.10 ...................... kwargs = {'logged_in_user': <SimpleLazyObject: <User: andy22>>}
09:44:51.10 ...................... len(kwargs) = 1
09:44:51.10 ...................... function = <function get_repo_tree at 0x10d71b7b8>
09:44:51.10 ...................... self = <snoop.configuration.Config.__init__.<locals>.ConfiguredTracer object at 0x10d5bd358>
09:44:51.10 <<< Exit with block in Tracer.__call__.<locals>.simple_wrapper

When the second snoop config is configured to go soley to a file,

snoop.install()
config = snoop.Config(out="junk.txt")

the nice normal snoop output fails to appear in stderr at all, and the two unwanted "tracer call outputs" appear in stderr instead. Output does thankfully appear in the output file.

I'm assuming I am using snoop correctly, by adding the two decorators one after the other, before the target def. If I don't add the second snoop decorator then it doesn't take effect, so it seems that must be the way to do it.

module not found

created a virtual environment.

/Users/soft_orca/.leetcode/venv/bin/python3.8 -m pip install snoop

import snoop at top of file

run code -> get error.
ImportError: No module named snoop

AttributeError on static method

Name: snoop
Version: 0.2.1

import snoop


class A:

    @snoop
    @staticmethod
    def print_hello():
        print('hello')


if __name__ == '__main__':
    A.print_hello()
$ python script.py
Traceback (most recent call last):
  File "script.py", line 5, in <module>
    class A:
  File "script.py", line 8, in A
    @staticmethod
  File "/home/q/envs/mitmproxy_image/lib/python3.6/site-packages/snoop/tracer.py", line 164, in __call__
    self.target_codes.add(function.__code__)
AttributeError: 'staticmethod' object has no attribute '__code__'

e: the fix is to put snoop after staticmethod decorator

    @staticmethod
    @snoop
    def print_hello():
        print('hello')

Solve 'Problem' section in Visual Studio Code

The lib is fantastic, thanks.
One tiny thing. Is it possible to solve 'Problems' section in Visual Studio Code? Now it gives me 'Module is not callable'. Persists regardless of using install or shortcuts.

Maybe I do something wrong?
Thank you once again.

upload wheel to pypi

it will be good if the snoop whl will be in pypi (useful for some projects, like pyodide that fetch pypi)

broken color in vim

I'm very happy that I found snoop, a very useful debug tool for python!
But I found a problem about color, code:

` # torchsnooper.register_snoop()

snoop.install(out='/transformer_scst/snoop.log', color='vim')
@snoop()
def myfunc(mask, x):
    model = torch.nn.Linear(10, 2)
    logits = mask + x
    y_pred = model(logits)
    return y_pred

mask = torch.randn((10000, 10))
source = torch.randn((10000, 10))
y = myfunc(mask, source)`

But when I see log file use cat commant, the color is normal:
image

but when I use vim ,the color is abnormal:
image

How can I solve this problem?
Thank you!

birdseye IS 3.8 compatible, apparently

I see the following, running under Python 3.8:

Traceback (most recent call last):
  File "route.py", line 73, in <module>
    def handle_change(change: ChangeType) -> None:
  File "/usr/local/lib/python3.8/site-packages/snoop/tracer.py", line 290, in __call__
    raise Exception("birdseye doesn't support this version of Python")
Exception: birdseye doesn't support this version of Python

Going by this, that isn't the case. Can the exception be removed?

The output contains SOURCE IS UNAVAILABLE๏ผŸ

After the format of py file is formatted by python-black๏ผŒthe output not contain the code of the original program.
As follow:
18:41:59.34 ...... self = <main.Solution object at 0x11f297460>
18:41:59.34 ...... nums = [2, 0, 2, 1, 1, 0]
18:41:59.34 ...... len(nums) = 6
18:41:59.34 27 | SOURCE IS UNAVAILABLE
18:41:59.34 29 | SOURCE IS UNAVAILABLE
18:41:59.34 ...... n = 6
18:41:59.34 32 | SOURCE IS UNAVAILABLE
18:41:59.34 ...... p = (6,)
18:41:59.34 ...... len(p) = 1
18:41:59.34 33 | SOURCE IS UNAVAILABLE
18:41:59.34 ...... res = []
18:41:59.34 34 | SOURCE IS UNAVAILABLE
18:41:59.36 ...... j = 0
18:41:59.36 35 | SOURCE IS UNAVAILABLE

Output buffering is altered

When using the snoop decorator in docker compose, all my output appears to be buffered, meaning there are significant delays in when the log output is shown.

It is not only the snoop output which is affected, but also normal logging statements. It affects buffering in the entire python application, not just the decorated functions.

If the package is installed, but nothing is decorated, there is no adverse effect.

Broken coloring when used with `tqdm`

I see that when I do from tqdm import tqdm and then run pp(a_variable)
There is no color for the pp() message anymore. So my work around was to import tqdm as late as possible. Is this problem with snoop or tqdm? How can we fix this?
This happens on Windows 10 command prompt.

tqdm Link: https://github.com/tqdm/tqdm

Somehow wrapping my pytorch tensor in pp breaks it when training on gpu

I added one line similar to this: pp(my_tensor) to view the contents of my pytorch tensor when on gpu but it returns the following error:

terminate called after throwing an instance of 'c10::Error'
  what():  CUDA error: initialization error
Exception raised from insert_events at /opt/conda/conda-bld/pytorch_1607369981906/work/c10/cuda/CUDACachingAllocator.cpp:717 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::string) + 0x42 (0x7f11b91498b2 in /home/nxingyu2/miniconda3/envs/NLP/lib/python3.8/site-packages/torch/lib/libc10.so)
frame #1: c10::cuda::CUDACachingAllocator::raw_delete(void*) + 0x1070 (0x7f11b939bf20 in /home/nxingyu2/miniconda3/envs/NLP/lib/python3.8/site-packages/torch/lib/libc10_cuda.so)
frame #2: c10::TensorImpl::release_resources() + 0x4d (0x7f11b9134b7d in /home/nxingyu2/miniconda3/envs/NLP/lib/python3.8/site-packages/torch/lib/libc10.so)
frame #3: <unknown function> + 0x5f9e52 (0x7f11fa901e52 in /home/nxingyu2/miniconda3/envs/NLP/lib/python3.8/site-packages/torch/lib/libtorch_python.so)
<omitting python frames>

Not sure if this is something to post here, but would like to raise it in case anyone else face a similar issue

Syntax for arguments in the %%snoop IPython magic command?

Currently it's not possible to pass any arguments to %%snoop. I'd like it to have the same arguments as snoop(). But magic commands are not functions, and arguments work differently. The command simply receives the line containing %%snoop as a string which is then parsed. The convention for most commands is to use shell-like syntax for arguments which are parsed with argparse. So the command might look something like:

%%snoop --depth 2 --watch self.foo,bar[0] --watch-explode thing,bob

Does that look good? I'm not sure I like this convention personally, but I don't use IPython much.

What about short versions of arguments? Should I provide them at all? How do these look?

  • -d for --depth
  • -w for --watch
  • -e (or -x?) for --watch-explode

deprecation warnings in snoop

A paste may be simpler than trying to describe it in words:

/home/mats/.pyenv/versions/python-3.7.3/lib/python3.7/site-packages/snoop/configuration.py:5: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Set, Mapping, Sequence
/home/mats/.pyenv/versions/python-3.7.3/lib/python3.7/site-packages/executing.py:18: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import defaultdict, namedtuple, Sized

Why did I execute snoop.instll() but it didn't work?

I have executed the corresponding command

image

But when I try to run the command in other files, it fails

image
image

I have a windows computer and the python interpreter version is 3.9.6 (I admit this version seems a bit high)
Can anyone solve my problem?
Thank you very much!

Discussion: visual design

The current look of snoop is the result of many small decisions and I really don't know if I made the right ones. What would you change about the appearance if you could? This could be about color, layout, the choice of words or special characters, and more. No detail is too small to post about here. Please also vote on proposals with reactions to help me see how popular they are.

snoop exist in a for loop after 10 iterations

I am using snoop and its very useful

I have a list variable of 20k items. I just want to see the tracing for few iterations only.

@snoop
def mapping(inputlist):
    testing = {}
    for eachDict in inputlist:
        for k, v in eachDict.items():
            testing[k] = v
            # <-------  want to exist snoop after 5 iterations
        # <-------  want to exist snoop after 10 iterations
    return testing

HOw can i achieve the above thing

How to debug coroutines?

The title is self explanatory. How to debug coroutines, tasks, futures using snoop or whatever else?
Asking this because snoop, as well as birdseye deliberately chose to not support coroutines.

snoop only in certain contexts

I came across an interesting idea a debugging library called behold which might be nice for snoop. Suppose you have a function foo() which gets called in many places, but you only want to debug it when it gets called from bar(). You could write:

def bar():
    foo()

@snoop(context='bar')
def foo():
    ...

Or maybe the context depends on a condition:

def bar():
    for i in range(10):
        with snoop.context('five', when=i == 5):
            foo()

@snoop(context='five')
def foo(i):
    ...

Or (forgetting 'context' for a moment) the condition could be declared by snoop based on arguments:

@snoop(when='i == 5')
def foo(i):
    ...

You might sometimes want to test for the context manually:

def foo(i):
    if snoop.context('bar'):
        pp(i)

Lots of possibilities here. Does anyone think this kind of thing seems useful? Ideally, have you encountered any real use cases? Is it worth bloating the library? What would be a good API?

Enable snoop during a recursive trace

Hi,

I'm working on a debugging toolbox which includes snoop (as well as Birdseye). One of the features is to automatically wrap a function that raises while tracing and, since there's no way to move execution back to the point before the function was run (without maybe some arcade bytecode hacking), eval the wrapped function again during the handling of the exception. One of the things that wrapper does is wrap the function with spy before running it again. I find that Birdseye works fine, however snoop provides no feedback. Is this the intended behavior? How can I work around it?

Should long exceptions be truncated?

When I wrote the code for PySnooper to display exception messages, I chose to truncate the message if it was too long. That's still the current behaviour of PySnooper. When writing snoop, I changed my mind, so snoop shows the whole message. I'm not sure which is right.

On the one hand, there may be situations where users want information in the middle of a long message, and if they don't, scrolling past it is not that hard. On the other hand, messages can get arbitrarily long, and this could be really annoying if they are raised many times. When not using snoop, those exceptions may be caught so they're not visible, so turning on snoop and being flooded with long exceptions would be an unpleasant surprise.

What do you think? If I turned on truncation again, what would be a good default limit?

Output in HTML format?

Hi,

does snoop support output in HTML format?

I think this would be really cool: Local variables hidden first, and only if you want to see them you can expand them.

Can i snoop celery tasks

I am trying to use snoop to debug celery tasks

@celery.task(name="connector-task")
@snoop
def test(param):
    a = param

It does not work.

How to: expand to see complete parameter

Hi, thankyou for snoop and depth setting. Super helpful.is there a setting that allows me to see entire parameter/ variable ?

For example : DeviceId='9...
deviceid is 128 characters and I would like to see it all.

Thankyou
Python 3.9, Mac 10.13.6

Add a comparison mode: disable timestamps, obscure 0xblah repr pointers, & canonicalize pathnames

My first use of snoop was to compare execution logs for a buggy area of the code between two runs to figure out where their behavior diverged. (A weak form of co-execution I suppose)

But before I could use diff, I had to post process the snoop output to remove the timestamps, obscure the repr's that contain 0x7f267b0a00 style memory addresses, and canonicalize the pathnames in str reprs that contained them as they differed between each execution environment.

It'd be nice to have a way to do a lot of this upfront via parameters to snoop() itself. I propose both of these:

  • Add the ability to omit timestamps.
  • Accept a repr post-processing callable.

The latter would allow for hex pointer value matching and cleanup as well as pathname cleanup. A pointer value cleanup only function could be provided as an example. Beyond that it's probably getting individual use case and application specific as to what people need to sanitize to make execution log comparisons easy.

Pyinstaller Freezing Error

when freezing with pyinstaller (single file) on windows a NameError: name 'copyright' is not defined appears. Not sure if this is an issue with hidden imports or hooks, but I wasn't able to get it to work with a simple

import snoop

@snoop
def counter(start, end):
    for i in range(start, end):
        print(i)
        
if __name__ == "__main__":
    counter(1, 1000)

Trace recursively, but only my code

Hi,

I would like to trace the http request handling of Django in my development environment.

But I am sure that the bug is in my code, not in the code of Django.

I would like to see every line of my code, excluding Django, standard library and other libraries.

I had a look at the arguments which snoop() accepts, but I think it is not possible up to now.

Would you accept a PR which implements this?

Do you have a hint how to implement this?

snoop prevents breakpoints working in vscode

Using any snoop decorators prevents breakpoints from working in Visual Studio Code. The vscode debugger outputs this warning:

PYDEV DEBUGGER WARNING:
sys.settrace() should not be used when the debugger is being used.
This may cause the debugger to stop working correctly.

This also happens with pysnooper.

A workaround is to snoop.install(enabled=False) which allows the vscode debugger breakpoints to work. Thankfully this centralised one line 'install' call disables all the snoop decorators globally - with pysnooper I had to hunt down all my decorators and actually comment them out!

Conveniences for tracing entire classes or files

Sometimes you may want to trace large chunks of your project without having to stick a decorator on each one and without setting a high depth which would trace lots of functions you don't care about. Below are some possible APIs. Which would you like? Any alternative ideas?

  1. Decorate a class to trace all of its methods. Simply put @snoop at the top of the class. The question is, what would this do?

    1. Decorate all functions defined directly in the class by parsing the file and looking at the locations of function definitions. Involves some magic but is very doable.
    2. Decorate all functions in the class __dict__, ignoring functions in base classes, but including functions that have been set as attributes rather than defined directly in the class.

    I like option (i) because it can bypass decorators on the methods and access the original underlying functions. Someone decorating a class may not consider that function decorators can get in the way and be surprised when a function doesn't get snooped - they might even think it's not being called. On the other hand it would surprise experienced Python developers who would intuitively expect something like (ii). It would also require leaving the tracing function on at all times which would hurt performance and get in the way of other debuggers.

    I could also provide both options and document them, but I'd prefer to keep the API lean and these concepts are not easy to explain concisely.

  2. Call a function like snoop.this_file() which snoops all methods in the file. Again there are options similar to (i) and (ii) above, either tracing the functions defined statically in the file or dynamically looking at the global variables, descending into classes. The first option would again require keeping the tracer on at all times.

  3. Call a function like snoop.files(lambda filename: some_condition), e.g. snoop.files(lambda filename: "my_package" in filename), to trace all files satisfying the condition. Hypothetically this could again have options like (i) and (ii), but it would almost certainly be (i).

Stale output when python file changes during runtime

I'm writing a live-reloading library on top of python which compiles and executes the same file each time it is saved. When running snoop inside such a file we see that snoop keeps referring to the first saved version of the file even when it has been changed. I'm attaching code that reproduces this:

Setup

open('./live.py', 'w').write('''
import snoop
with snoop:
    x = 1
    y = 2
    print(x, y)
''')

exec(compile(open('./live.py').read(), 'live.py', 'exec'), {})

open('./live.py', 'w').write('''
import snoop
with snoop:
    x = 4
    y = 5
    print(x, y)
''')

exec(compile(open('./live.py').read(), 'live.py', 'exec'), {})

Observed output

17:10:01.04 >>> Enter with block in <module> in File "live.py", line 3
17:10:01.04 ...... __builtins__ = {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other object...nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, ...}
17:10:01.04 ...... len(__builtins__) = 152
17:10:01.04 ...... snoop = <class 'snoop.configuration.Config.__init__.<locals>.ConfiguredTracer'>
17:10:01.04    4 |     x = 1
17:10:01.04    5 |     y = 2
17:10:01.04    6 |     print(x, y)
1 2
17:10:01.04 <<< Exit with block in <module>
17:10:01.04 >>> Enter with block in <module> in File "live.py", line 3
17:10:01.04 ...... __builtins__ = {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other object...nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, ...}
17:10:01.04 ...... len(__builtins__) = 152
17:10:01.04 ...... snoop = <class 'snoop.configuration.Config.__init__.<locals>.ConfiguredTracer'>
17:10:01.04    4 |     x = 1
17:10:01.04 .......... x = 4
17:10:01.04    5 |     y = 2
17:10:01.04 .......... y = 5
17:10:01.04    6 |     print(x, y)
4 5
17:10:01.04 <<< Exit with block in <module>

Expected output

# ... same as above until:
17:10:01.04    4 |     x = 4
17:10:01.04 .......... x = 4
17:10:01.04    5 |     y = 5
17:10:01.04 .......... y = 5
17:10:01.04    6 |     print(x, y)
4 5
17:10:01.04 <<< Exit with block in <module>

Snoop doesn't load source code from compiled functions

I'm trying to debug functions, generated from the source in runtime, but for some reason, snoop is unable to show the source code of such function. At the same time it shows variables and nested functions source - see an example below. Please, suggest, if I'm doing something wrong? Thanks!

Script output

    22:03:23.24 >>> Call to main in File "func_code", line 1
    22:03:23.24    1 | SOURCE IS UNAVAILABLE
    22:03:23.24    2 | SOURCE IS UNAVAILABLE
    22:03:23.24 ...... a = 2
    22:03:23.24    3 | SOURCE IS UNAVAILABLE
    22:03:23.24 ...... b = 3
    22:03:23.24    5 | SOURCE IS UNAVAILABLE
        22:03:23.33 >>> Call to Random.randint in File "/usr/local/Cellar/[email protected]/3.8.2/Frameworks/Python.framework/Versions/3.8/lib/python3.8/random.py", line 244
        22:03:23.33 .......... self = <random.Random object at 0x7ff952003810>
        22:03:23.33 .......... a = 1
        22:03:23.33 .......... b = 100
        22:03:23.33  244 |     def randint(self, a, b):
        22:03:23.34  248 |         return self.randrange(a, b+1)
        22:03:23.34 <<< Return value from Random.randint: 20
    22:03:23.34    5 | SOURCE IS UNAVAILABLE
    22:03:23.34 ...... c = 25
    22:03:23.34    7 | SOURCE IS UNAVAILABLE
    22:03:23.34 <<< Return value from main: 25

Script code

import types
import random

import snoop


func_text = """def main():
        a = 2
        b = 3
        c = a + b + random.randint(1, 100)
        return c"""

func_code = compile(
    source=func_text,
    filename="func_code",
    mode='exec',
)

tracer = snoop.snoop(depth=3)

func = types.FunctionType(
    func_code.co_consts[0],
    {
        "random": random,
        "tracer": tracer,
    },
    'func'
)


if __name__ == '__main__':
    with tracer:
        func()

How to snoop entire file

Current to use scoop i have to craete a functions

import snoop

@snoop
def main():
    ALLOWED_HOSTS=[
        "host1",
        "host2"
    ]
    return ALLOWED_HOSTS

hare = main()

instead can i do or entire file

import snoop
@snoop

any python code here

just like in bash we have

set -x

bash script


to see all the commands

Color in Jupyter Notebooks

Colors in notebooks don't look great:

Screen Shot 2019-07-21 at 19 07 43

Some thoughts:

  1. A good automatic default solution will probably require reliably detecting that we are in a notebook.
  2. The default red background for stderr is a problem for pretty much any color scheme. It's a bit better if you set out=sys.stdout. The default colors for snoop don't work well on a white background, but you can choose a new theme here and pass it to color, e.g. color='manni'.
  3. It's possible to set a different theme for notebooks. A dark theme such as chesterish mostly works well with snoop's default colors.
  4. The prefix of lines (the time etc.) are colored in 'grey' (escape code '\x1b[90m') which terminals can actually choose to display however they want. In actual terminals this seems to work quite well. In Jupyter it's a complete failure - the default theme renders it pretty much black, so it doesn't get de-emphasised as it should, while in every dark theme I've tried it's unreadable. It might be best to use an escape code that specifies an RGB value so it always looks the same. Solved in #14

Showing tracebacks

Sometimes you may want to see a traceback in the snoop logs when an exception is raised (right?). Some questions to consider:

  1. Should tracebacks sometimes/always show automatically, or only when they've been turned on? Should they be turned on in snoop, install, or both?
  2. Should they show whenever they are raised, or only when they end a call?
  3. Should they show in any traced call, including inner calls that are only traced because of a high depth, or only in functions directly traced with @snoop or with snoop?
  4. Should there be a function to log the traceback manually? This could be used even if there isn't an exception being handled, just to show the current stack. What should it be called?

A question before using: can this work with pdb step by step debugging

Hello,
from the descriptions, this project looks great. I have a particular use case i wanted to know snoop can work for.
I have to do step by step debugging with pdb for data scientific applications, and it'd be great if snoop can print out its details every time i do 's', 'c' or 'n'. is this part of the functionality?
thanks

collections.abc deprecations for Python 3.8

I was running pytest and saw this warning:

.venv\lib\site-packages\snoop\variables.py:2
  c:\project\.venv\lib\site-packages\snoop\variables.py:2: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import Mapping, Sequence

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.