Code Monkey home page Code Monkey logo

ionelmc / python-aspectlib Goto Github PK

View Code? Open in Web Editor NEW
109.0 8.0 11.0 698 KB

An aspect-oriented programming, monkey-patch and decorators library. It is useful when changing behavior in existing code is desired. It includes tools for debugging and testing: simple mock/record and a complete capture/replay framework.

License: BSD 2-Clause "Simplified" License

Python 100.00%
python debugging aop aspect-oriented-programming aspect-oriented-framework monkey-patching decorators decorator

python-aspectlib's Introduction

Overview

docs Documentation Status
tests
GitHub Actions Build Status Requirements Status
Coverage Status Coverage Status
package
PyPI Package latest release PyPI Wheel Supported versions Supported implementations
Commits since latest release

aspectlib is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing behavior in existing code is desired. It includes tools for debugging and testing: simple mock/record and a complete capture/replay framework.

  • Free software: BSD 2-Clause License

Installation

pip install aspectlib

You can also install the in-development version with:

pip install https://github.com/ionelmc/python-aspectlib/archive/main.zip

Documentation

Docs are hosted at readthedocs.org: python-aspectlib docs.

Implementation status

Weaving functions, methods, instances and classes is completed.

Pending:

  • "Concerns" (see docs/todo.rst)

If aspectlib.weave doesn't work for your scenario please report a bug!

Requirements

OS

Any

Runtime

Python 2.6, 2.7, 3.3, 3.4 or PyPy

Python 3.2, 3.1 and 3.0 are NOT supported (some objects are too crippled).

Similar projects

python-aspectlib's People

Contributors

felixonmars avatar german1608 avatar ionelmc avatar jdelic avatar svetlyak40wt avatar the-compiler 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

python-aspectlib's Issues

Tests fail: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated

===>   py39-aspectlib-2.0.0 depends on file: /usr/local/bin/python3.9 - found
cd /usr/ports/devel/py-aspectlib/work-py39/aspectlib-2.0.0 && /usr/bin/env XDG_DATA_HOME=/usr/ports/devel/py-aspectlib/work-py39  XDG_CONFIG_HOME=/usr/ports/devel/py-aspectlib/work-py39  XDG_CACHE_HOME=/usr/ports/devel/py-aspectlib/work-py39/.cache  HOME=/usr/ports/devel/py-aspectlib/work-py39 PATH=/usr/local/libexec/ccache:/usr/ports/devel/py-aspectlib/work-py39/.bin:/home/yuri/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin PKG_CONFIG_LIBDIR=/usr/ports/devel/py-aspectlib/work-py39/.pkgconfig:/usr/local/libdata/pkgconfig:/usr/local/share/pkgconfig:/usr/libdata/pkgconfig MK_DEBUG_FILES=no MK_KERNEL_SYMBOLS=no SHELL=/bin/sh NO_LINT=YES PREFIX=/usr/local  LOCALBASE=/usr/local  CC="cc" CFLAGS="-O2 -pipe  -fstack-protector-strong -fno-strict-aliasing "  CPP="cpp" CPPFLAGS=""  LDFLAGS=" -fstack-protector-strong " LIBS=""  CXX="c++" CXXFLAGS="-O2 -pipe -fstack-protector-strong -fno-strict-aliasing  "  MANPREFIX="/usr/local" CCACHE_DIR="/tmp/.ccache" BSD_INSTALL_PROGRAM="install  -s -m 555"  BSD_INSTALL_LIB="install  -s -m 0644"  BSD_INSTALL_SCRIPT="install  -m 555"  BSD_INSTALL_DATA="install  -m 0644"  BSD_INSTALL_MAN="install  -m 444" PYTHONPATH=/usr/ports/devel/py-aspectlib/work-py39/stage/usr/local/lib/python3.9/site-packages /usr/local/bin/python3.9 -m pytest -k '' -rs -v -o addopts= 
Test session starts (platform: freebsd13, Python 3.9.16, pytest 7.3.1, pytest-sugar 0.9.7)
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/usr/ports/devel/py-aspectlib/work-py39/aspectlib-2.0.0/.hypothesis/examples')
Using --randomly-seed=69246004
rootdir: /usr/ports/devel/py-aspectlib/work-py39/aspectlib-2.0.0
configfile: pytest.ini
testpaths: tests
plugins: aspectlib-2.0.0, forked-1.6.0, hypothesis-6.75.9, mypy-plugins-1.11.1, cov-2.9.0, randomly-3.12.0, timeout-2.1.0, xdist-2.5.0, rerunfailures-11.1.2, flaky-3.7.0, env-0.6.2, mock-3.10.0, anyio-3.7.0, checkdocs-2.9.0, black-0.3.12, mypy-0.10.3, sugar-0.9.7, services-2.2.1, flake8-1.1.1, enabler-2.1.1, xdoctest-1.1.1
collecting 130 items / 1 error                                                                                                                                                                                                 
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ERROR collecting tests/test_integrations.py ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
tests/test_integrations.py:9: in <module>
    from process_tests import dump_on_error
/usr/local/lib/python3.9/site-packages/process_tests.py:26: in <module>
    import unittest2 as unittest
/usr/local/lib/python3.9/site-packages/unittest2/__init__.py:40: in <module>
    from unittest2.collector import collector
/usr/local/lib/python3.9/site-packages/unittest2/collector.py:3: in <module>
    from unittest2.loader import defaultTestLoader
/usr/local/lib/python3.9/site-packages/unittest2/loader.py:13: in <module>
    from unittest2 import case, suite, util
/usr/local/lib/python3.9/site-packages/unittest2/case.py:18: in <module>
    from unittest2 import result
/usr/local/lib/python3.9/site-packages/unittest2/result.py:10: in <module>
    from unittest2.compatibility import wraps
/usr/local/lib/python3.9/site-packages/unittest2/compatibility.py:143: in <module>
    class ChainMap(collections.MutableMapping):
/usr/local/lib/python3.9/collections/__init__.py:62: in __getattr__
    warnings.warn("Using or importing the ABCs from 'collections' instead "
E   DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
collected 130 items / 1 error                                                                                                                                                                                                  

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Results (3.15s):
*** Error code 2

Version: 2.0.0
Python: 3.9
FreeBSD 13.2

1.5.2: man page generation warnings

+ sphinx-build -b man -d aspectlib docs .
Running Sphinx v3.5.3
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: [new config] 18 added, 0 changed, 0 removed
reading sources... [100%] todo
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:11: WARNING: autosummary: failed to import aspectlib.Aspect
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:11: WARNING: autosummary: failed to import aspectlib.Proceed
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:11: WARNING: autosummary: failed to import aspectlib.Return
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:22: WARNING: autosummary: failed to import aspectlib.ALL_METHODS
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:22: WARNING: autosummary: failed to import aspectlib.NORMAL_METHODS
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:22: WARNING: autosummary: failed to import aspectlib.weave
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.rst.rst:22: WARNING: autosummary: failed to import aspectlib.Rollback
WARNING: autodoc: failed to import module 'aspectlib'; the following exception was raised:
No module named 'aspectlib'
WARNING: don't know which module to import for autodocumenting 'Aspect' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
WARNING: don't know which module to import for autodocumenting 'Rollback' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
WARNING: don't know which module to import for autodocumenting 'ALL_METHODS' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
WARNING: don't know which module to import for autodocumenting 'NORMAL_METHODS' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
WARNING: don't know which module to import for autodocumenting 'weave(target, aspect[, subclasses=True, methods=NORMAL_METHODS, lazy=False, aliases=True])' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.contrib.rst.rst:4: WARNING: autosummary: failed to import aspectlib.contrib.retry
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.contrib.rst.rst:4: WARNING: autosummary: failed to import aspectlib.contrib.retry.exponential_backoff
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.contrib.rst.rst:4: WARNING: autosummary: failed to import aspectlib.contrib.retry.straight_backoff
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.contrib.rst.rst:4: WARNING: autosummary: failed to import aspectlib.contrib.retry.flat_backoff
WARNING: autodoc: failed to import module 'contrib' from module 'aspectlib'; the following exception was raised:
No module named 'aspectlib'
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.debug.rst.rst:9: WARNING: autosummary: failed to import aspectlib.debug.log
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.debug.rst.rst:9: WARNING: autosummary: failed to import aspectlib.debug.format_stack
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.debug.rst.rst:9: WARNING: autosummary: failed to import aspectlib.debug.frame_iterator
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.debug.rst.rst:9: WARNING: autosummary: failed to import aspectlib.debug.strip_non_ascii
WARNING: autodoc: failed to import module 'debug' from module 'aspectlib'; the following exception was raised:
No module named 'aspectlib'
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.test.rst.rst:12: WARNING: autosummary: failed to import aspectlib.test.record
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.test.rst.rst:12: WARNING: autosummary: failed to import aspectlib.test.mock
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.test.rst.rst:12: WARNING: autosummary: failed to import aspectlib.test.Story
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/reference/aspectlib.test.rst.rst:12: WARNING: autosummary: failed to import aspectlib.test.Replay
WARNING: autodoc: failed to import module 'test' from module 'aspectlib'; the following exception was raised:
No module named 'aspectlib'
WARNING: don't know which module to import for autodocumenting 'Replay(?)' (try placing a "module" or "currentmodule" directive in the document, or giving an explicit module name)
looking for now-outdated files... none found
pickling environment... done
checking consistency... /home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/authors.rst: WARNING: document isn't included in any toctree
/home/tkloczko/rpmbuild/BUILD/aspectlib-1.5.2/docs/presentations/pycon.se-lightningtalk.rst: WARNING: document isn't included in any toctree
done
writing... python-aspectlib.3 { introduction installation testing rationale faq examples reference/index reference/aspectlib reference/aspectlib.contrib reference/aspectlib.debug reference/aspectlib.test development todo contributing changelog } done
build succeeded, 31 warnings.

Profiling Example

I wonder if it's possible to do profiling with 'debug.log' module

Pass many objects as target argument in aspectlib.weave method

Hello, I have the following piece of code:

if __name__ == '__main__':
   weaving_list = [ShannonDBClient,
                   columns.create_columns_in_bulk,
                   columns.create_single_column]
   with weave(weaving_list, print_debug_log, lazy=True):
       loop = asyncio.get_event_loop()
       loop.run_until_complete(main(args))

I would like to know if it is possible, instead I pass every method name in columns module, I just pass the module name, to reduce the weaving_list variable size.

Tests failed with Tornado 6

tornado.gen.Task was removed in Tornado 6.

=================================== FAILURES ===================================
_______________________ test_decorate_tornado_coroutine ________________________
tests/test_integrations_py3.py:43: in test_decorate_tornado_coroutine
    loop.run_sync(coro)
/usr/lib/python3.8/site-packages/tornado/ioloop.py:532: in run_sync
    return future_cell[0].result()
/usr/lib/python3.8/site-packages/tornado/gen.py:209: in wrapper
    yielded = next(result)
src/aspectlib/py35support.py:90: in advising_generator_wrapper_py35
    advice = advisor.throw(*sys.exc_info())
src/aspectlib/debug.py:220: in advising_function
    res = yield
src/aspectlib/py35support.py:88: in advising_generator_wrapper_py35
    result = yield from gen
tests/test_integrations_py3.py:39: in coro
    yield gen.Task(loop.add_timeout, timedelta(microseconds=10))
E   AttributeError: module 'tornado.gen' has no attribute 'Task'
------------------------------ Captured log call -------------------------------
CRITICAL aspectlib.debug:debug.py:218 coro()                                                        <<< aspectlib/py35support.py:79:advising_generator_wrapper_py35 < tornado/gen.py:209:wrapper
CRITICAL aspectlib.debug:debug.py:225 coro ~ raised AttributeError("module 'tornado.gen' has no attribute 'Task'")
=```

weave does not work with object in submodules under Py34

In [12]: from aspectlib.debug import log

In [13]: aspectlib.weave(log, aspectlib.contrib.retry())                                                                                
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-13-e01726c44eac> in <module>()
----> 1 aspectlib.weave(log, aspectlib.contrib.retry())

/home/imposeren/.virtualenvs/aiwona/lib/python3.4/site-packages/aspectlib/__init__.py in weave(target, aspects, **options)
    479         name = target.__name__
    480         logdebug("@ patching %r (%s) as a property.", target, name)
--> 481         func = owner.__dict__[name]
    482         return patch_module(owner, name, _checked_apply(aspects, func), func, **options)
    483     elif PY2 and isfunction(target):

KeyError: 'log'

this is caused because __qualname__ does not have module and __import__ only imports toplevel module (__import__('aspectlib.contrib') actually returns aspectlib module) and so '.contrib' part of path is never used. Possible fix:

-path = deque(target.__qualname__.split('.')[:-1])
+path = deque(target.__module__.split('.')[1:] + target.__qualname__.split('.')[:-1])

QUESTION: is it designed to wrap methods, funtions only? Can classes be wrapped?

Hey. I'm looking for exactly the same functionality aspectlib provides with. You've done good job!

Can you suggest a way of how to wrap a class to have all its methods handled by aspect's logic?

Something like this

def log_every_method_call(*args, **kawrgs):
    print("Call", args, kwargs)
    result = yield aspectlib.Proceed
    print("Result", result)


@log_every_method_call
class ComplexClass:
    ....

Stack trace includes aspectlib itself

It's easy to skip the aspectlib frames:

diff --git a/src/aspectlib/debug.py b/src/aspectlib/debug.py
index 2ee6225..85d13b5 100644
--- a/src/aspectlib/debug.py
+++ b/src/aspectlib/debug.py
@@ -20,7 +20,8 @@ def frame_iterator(frame):
     Yields frames till there are no more.
     """
     while frame:
-        yield frame
+        if "aspectlib" not in frame.f_code.co_filename.split(os.path.sep):
+            yield frame
         frame = frame.f_back 

But the tests fail because the frames they are looking for are excluded by this. Not sure how to fix that, other than adding an aspectlib=True flag to debug.log for the tests to use.

Invocation location info from Aspect?

I wonder if we can retrieve the invocation location info from aspect?
For example, when patching open(), there could be several places where open() is invoked. Is it possible to which open() is invoked from aspect?
Yes there's cutpoint passed in when using bind argument to @Aspect, but there seems no invocation location info(like file, line, etc.) in the cutpoint parameter.

weave does not work with "generated" functions

In [1]: def foo_factory(foo_name):                                                                                                                                                                                          
    def foo():
        pass
    foo.__name__ = foo_name
    return foo
   ...: 

In [2]: foo_func = foo_factory('foo_func')                                                                                                                                                                                    

In [3]: import aspectlib.contrib

In [4]: import aspectlib

In [5]: aspectlib.weave(foo_func, aspectlib.contrib.retry())                                                                                                                                   
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-03b12c5d089d> in <module>()
----> 1 aspectlib.weave(foo_func, aspectlib.contrib.retry())

/home/yaroslavklyuyev/.virtualenvs/aiwona/lib/python3.4/site-packages/aspectlib/__init__.py in weave(target, aspects, **options)
    474         path = deque(target.__qualname__.split('.')[:-1])
    475         while path:
--> 476             owner = getattr(owner, path.popleft())
    477         name = target.__name__
    478         logdebug("@ patching %r (%s) as a property.", target, name)

AttributeError: 'function' object has no attribute '<locals>'

In [6]: 

This issue can be solved by using dill.source.getname:

from demo import foo_func as custom_named_func
from dill.source import getname
from inspect import getmodule

module, object_name = getmodule(custom_named_func), getname(custom_named_func)
# results in <module 'demo' from '/some/path/demo.py'>, 'foo_func'

This can also be solved by setting __qualname__ in function factory. Maybe document it somewhere? Or better: raise exception that tells to set qualname when qualname contains '<locals>'

But still custom_named_func.__name__/custom_named_func.__qualname__ should match variable name or everything will fail

[Q] Is it still alive?

Good day

Looks like this library wasn't updated for last two years just as well some other libraries with a similar purpose. So, here is the question: is this project still alive? Can it be recommended for usage in new projects? And if not, why everybody suddenly decided to abandon AOP libraries for Python (i.e. what is an alternative approach to the usage of AOP libraries)?

Weaving flask's render_template function

I am working on building software product lines for Flask applications and I am trying to use an aspect to make Flask's render_template function render from another HTML file:

from flask import render_template, Blueprint, url_for, redirect, request
from flask.views import MethodView
...

from aspectlib import Aspect, Proceed, weave

...

bp = Blueprint('home', __name__, template_folder='../templates')

class MessageForm(Form):
    ...

@Aspect
def render_author(*args, **kwargs):
	print("Rendering page with author information...")
	yield Proceed('homeWithAuthor.html', **kwargs)

class HomeView(MethodView):

    def get(self):
        with weave(render_template, render_author):
            form = MessageForm(request.form)
            messages = db.session.execute(db.select(Message)).scalars()
            return render_template(
                "home.html", 
                form=form,
                messages=messages
            )

    def post(self):
        ...
    
bp.add_url_rule("/", view_func=HomeView.as_view("home"))

The app would render from the original file instead of the stated file in the aspect though. There were no errors in weaving and running the app. Upon inspection with a debugger, the aspect has not been called at all. I have tried other arguments in the weaver including

weave('render_template', render_author)
weave('flask.render_template', render_author)
weave(flask.render_template, render_author)
weave('flask', render_author, methods='render_template')

and they all have the same result. The weaver works with builtin functions and class methods so I think it is an issue of me not weaving the aspect correctly.

Any help is appreciated, thanks!

Python 3.10 has new error messages

The following tests fail with Python 3.10 due to a slightly different error message:

[   27s] =================================== FAILURES ===================================
[   27s] ______________________ test_story_empty_play_proxy_class _______________________
[   27s] tests/test_aspectlib_test.py:411: in test_story_empty_play_proxy_class
[   27s]     assert format_calls(replay._actual) == format_calls(OrderedDict([
[   27s] E   assert "stuff_1 = te...)  # raises\n" == "stuff_1 = te...)  # raises\n"
[   27s] E       stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
[   27s] E       stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
[   27s] E       stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns
[   27s] E     - stuff_1.meth(123) ** TypeError('meth() takes 1 positional argument but 2 were given')  # raises
[   27s] E     + stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s] E     ?                                 ++++++
[   27s] E       stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)...
[   27s] E     
[   27s] E     ...Full output truncated (6 lines hidden), use '-vv' to show
[   27s] ----------------------------- Captured stdout call -----------------------------
[   27s] STORY/REPLAY DIFF:
[   27s]     --- expected
[   27s]     +++ actual
[   27s]     @@ -0,0 +1,8 @@
[   27s]     +stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
[   27s]     +stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
[   27s]     +stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns
[   27s]     +stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s]     +stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
[   27s]     +stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
[   27s]     +stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
[   27s]     +stuff_2.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s] ACTUAL:
[   27s]     stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
[   27s]     stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
[   27s]     stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns
[   27s]     stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s]     stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
[   27s]     stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
[   27s]     stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
[   27s]     stuff_2.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s] 
[   27s] _______________________ test_story_half_play_proxy_class _______________________
[   27s] tests/test_aspectlib_test.py:448: in test_story_half_play_proxy_class
[   27s]     assert replay.unexpected == format_calls(OrderedDict([
[   27s] E   assert "stuff_1.meth...)  # raises\n" == "stuff_1.meth...)  # raises\n"
[   27s] E       stuff_1.meth() == None  # returns
[   27s] E     - stuff_1.meth(123) ** TypeError('meth() takes 1 positional argument but 2 were given')  # raises
[   27s] E     + stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s] E     ?                                 ++++++
[   27s] E       stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
[   27s] E       stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
[   27s] E       stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns...
[   27s] E     
[   27s] E     ...Full output truncated (4 lines hidden), use '-vv' to show
[   27s] ----------------------------- Captured stdout call -----------------------------
[   27s] STORY/REPLAY DIFF:
[   27s]     --- expected
[   27s]     +++ actual
[   27s]     @@ -1,2 +1,8 @@
[   27s]      stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
[   27s]      stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
[   27s]     +stuff_1.meth() == None  # returns
[   27s]     +stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s]     +stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
[   27s]     +stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
[   27s]     +stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
[   27s]     +stuff_2.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s] ACTUAL:
[   27s]     stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
[   27s]     stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
[   27s]     stuff_1.meth() == None  # returns
[   27s]     stuff_1.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises
[   27s]     stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
[   27s]     stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
[   27s]     stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
[   27s]     stuff_2.meth(123) ** TypeError('Stuff.meth() takes 1 positional argument but 2 were given')  # raises

Weave "AttributeError" on AWS Lambda

Hi!
Great library - thanks for your work!
I'm hosting some Python 3.6 code on AWS Lambda and I have a strangely intermittent error (occurs about 80% of the time) with aspectlib weave on the Python Requests library. Here's the exception:

File "/var/task/aspectlib/__init__.py", line 413, in weave
weave(item, aspects, **options) for item in target
File "/var/task/aspectlib/__init__.py", line 413, in <listcomp>
weave(item, aspects, **options) for item in target
File "/var/task/aspectlib/__init__.py", line 505, in weave
return weave_instance(target, aspects, **options)
File "/var/task/aspectlib/__init__.py", line 554, in weave_instance
patch_module(instance, attr, _checked_apply(fixed_aspect, realfunc, module=None), **options)
File "/var/task/aspectlib/__init__.py", line 722, in patch_module
setattr(module, alias, replacement)
AttributeError: '__logged__' object attribute 'advising_function' is read-only

Here's my weave call:

weave([Session.send],
      debug.log(stacktrace=False),
      lazy=True)

Never have a problem on my Ubuntu 18 dev machine. Don't have a problem on a normal hosting environment - such as an EC2 instance. This problem only seems to occur on AWS Lambda.

I have made a patch to catch the exception and debug log the error. Please see the pull request. The catch is not very specific; I wanted to avoid recording the rollback of the weave and the "seen = True" part.

Test failures under Python 3.7

============================= test session starts ==============================
platform linux -- Python 3.7.0, pytest-3.6.3, py-1.5.4, pluggy-0.6.0
rootdir: /build/python-aspectlib/src/python-aspectlib, inifile: setup.cfg
plugins: capturelog-0.7
collected 157 items

docs/examples.rst .                                                      [  0%]
docs/testing.rst .                                                       [  1%]
docs/presentations/pycon.se-lightningtalk.rst .                          [  1%]
docs/reference/aspectlib.rst F                                           [  2%]
src/aspectlib/__init__.py .                                              [  3%]
src/aspectlib/contrib.py .                                               [  3%]
src/aspectlib/debug.py F                                                 [  4%]
src/aspectlib/test.py FF...                                              [  7%]
tests/test_aspectlib.py ......................s......................... [ 38%]
..s............F....FFF..FFF....                                         [ 58%]
tests/test_aspectlib_debug.py .........                                  [ 64%]
tests/test_aspectlib_py3.py FFFF                                         [ 66%]
tests/test_aspectlib_test.py ......................F........             [ 86%]
tests/test_contrib.py .......F                                           [ 91%]
tests/test_integrations.py ...........                                   [ 98%]
tests/test_integrations_py3.py F.                                        [100%]

=================================== FAILURES ===================================
___________________________ [doctest] aspectlib.rst ____________________________
094             ...         print("Raised %r for %s/%s" % (exc, args, kwargs))
095             ...         raise
096             ...     else:
097             ...         print("Returned %r for %s/%s" % (value, args, kwargs))
098 
099             >>> @log_results
100             ... def weird_function():
101             ...     yield 1
102             ...     raise StopIteration('foobar')  # in Python 3 it's the same as: return 'foobar'
103             >>> list(weird_function())
UNEXPECTED EXCEPTION: RuntimeError('generator raised StopIteration')
Traceback (most recent call last):

  File "<doctest aspectlib.rst[9]>", line 4, in weird_function

StopIteration: foobar


The above exception was the direct cause of the following exception:


Traceback (most recent call last):

  File "/usr/lib/python3.7/doctest.py", line 1329, in __run
    compileflags, 1), test.globs)

  File "<doctest aspectlib.rst[10]>", line 1, in <module>

  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/py3support.py", line 41, in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())

  File "<doctest aspectlib.rst[8]>", line 4, in log_results

  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/py3support.py", line 39, in advising_generator_wrapper_py3
    result = yield from gen

RuntimeError: generator raised StopIteration

/build/python-aspectlib/src/python-aspectlib/docs/reference/aspectlib.rst:103: UnexpectedException
________________________ [doctest] aspectlib.debug.log _________________________
101     Example::
102 
103         >>> @log(print_to=sys.stdout)
104         ... def a(weird=False):
105         ...     if weird:
106         ...         raise RuntimeError('BOOM!')
107         >>> a()
108         a()                                                           <<< ...
109         a => None
110         >>> try:
Expected:
    a(weird=True)                                                 <<< ...
    a ~ raised RuntimeError('BOOM!',)
Got:
    a(weird=True)                                                 <<< aspectlib/__init__.py:257:advising_function_wrapper < aspectlib/debug.py:177:__call__ < <doctest aspectlib.debug.log[2]>:2:<module> < python3.7/doctest.py:1329:__run < python3.7/doctest.py:1475:run < python3.7/doctest.py:1838:run < _pytest/doctest.py:194:runtest < _pytest/runner.py:109:pytest_runtest_call < pluggy/callers.py:180:_multicall < pluggy/__init__.py:216:<lambda>
    a ~ raised RuntimeError('BOOM!')

/build/python-aspectlib/src/python-aspectlib/src/aspectlib/debug.py:110: DocTestFailure
------------------------------ Captured log call -------------------------------
debug.py                   217 CRITICAL a()                                                           <<< aspectlib/__init__.py:257:advising_function_wrapper < aspectlib/debug.py:177:__call__ < <doctest aspectlib.debug.log[1]>:1:<module> < python3.7/doctest.py:1329:__run < python3.7/doctest.py:1475:run < python3.7/doctest.py:1838:run < _pytest/doctest.py:194:runtest < _pytest/runner.py:109:pytest_runtest_call < pluggy/callers.py:180:_multicall < pluggy/__init__.py:216:<lambda>
debug.py                   228 CRITICAL a => None
debug.py                   217 CRITICAL a(weird=True)                                                 <<< aspectlib/__init__.py:257:advising_function_wrapper < aspectlib/debug.py:177:__call__ < <doctest aspectlib.debug.log[2]>:2:<module> < python3.7/doctest.py:1329:__run < python3.7/doctest.py:1475:run < python3.7/doctest.py:1838:run < _pytest/doctest.py:194:runtest < _pytest/runner.py:109:pytest_runtest_call < pluggy/callers.py:180:_multicall < pluggy/__init__.py:216:<lambda>
debug.py                   224 CRITICAL a ~ raised RuntimeError('BOOM!')
_____________________ [doctest] aspectlib.test.LogCapture ______________________
079 
080     Example::
081 
082         >>> import logging
083         >>> logger = logging.getLogger('mylogger')
084         >>> with LogCapture(logger, level='INFO') as logs:
085         ...     logger.debug("Message from debug: %s", 'somearg')
086         ...     logger.info("Message from info: %s", 'somearg')
087         ...     logger.error("Message from error: %s", 'somearg')
088         >>> logs.calls
Expected:
    [('Message from info: %s', ('somearg',), 'INFO'), ('Message from error: %s', ('somearg',), 'ERROR')]
Got:
    [('Message from error: %s', ('somearg',), 'ERROR')]

/build/python-aspectlib/src/python-aspectlib/src/aspectlib/test.py:88: DocTestFailure
------------------------------ Captured log call -------------------------------
<doctest aspectlib.test.LogCapture[2]>    4 ERROR    Message from error: somearg
__________________ [doctest] aspectlib.test.Replay.unexpected __________________
EXAMPLE LOCATION UNKNOWN, not showing all tests of that example
??? >>> print(replay.unexpected)
Differences (unified diff with -expected +actual):
    @@ -1,3 +1,3 @@
     mymod.func('some arg') == None  # returns
    -mymod.badfunc() ** ValueError('boom!',)  # raises
    +mymod.badfunc() ** ValueError('boom!')  # raises
     <BLANKLINE>

/build/python-aspectlib/src/python-aspectlib/src/aspectlib/test.py:None: DocTestFailure
_________________ test_aspect_on_generator_raise_stopiteration _________________
tests/test_aspectlib.py:1389: in func
    raise StopIteration('something')
E   StopIteration: something

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1392: in test_aspect_on_generator_raise_stopiteration
    assert list(func()) == []
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1384: in aspect
    val = yield aspectlib.Proceed
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
_________________ test_aspect_on_generator_result_from_aspect __________________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: result

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1503: in test_aspect_on_generator_result_from_aspect
    next(gen)
E   RuntimeError: generator raised StopIteration
_______________________ test_aspect_on_generator_result ________________________
tests/test_aspectlib.py:1520: in func
    raise StopIteration('value')
E   StopIteration: value

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1522: in test_aspect_on_generator_result
    assert list(func()) == ['something']
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1515: in aspect
    result.append((yield aspectlib.Proceed))
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
___________________________ test_aspect_on_coroutine ___________________________
tests/test_aspectlib.py:1567: in test_aspect_on_coroutine
    assert hist == ['before', 'the-return-value', 'after', 'finally', 'closed']
E   AssertionError: assert ['before', 'e...ly', 'closed'] == ['before', 'th...ly', 'closed']
E     At index 1 diff: 'error' != 'the-return-value'
E     Right contains more items, first extra item: 'closed'
E     Use -v to get the full diff
----------------------------- Captured stdout call -----------------------------
YIELD 100
GOT 0
YIELD 1
GOT 1
YIELD 2
GOT 2
[100, 1, 2, 'done']
['before', 'error', 'finally', 'closed']
________________________ test_aspect_chain_on_generator ________________________
tests/test_aspectlib.py:1598: in func
    raise StopIteration(a)
E   StopIteration: 3

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1602: in test_aspect_chain_on_generator
    result = pytest.raises(StopIteration, gen.__next__ if hasattr(gen, '__next__') else gen.next)
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1590: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1590: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1590: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
___________________ test_aspect_chain_on_generator_no_return ___________________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: None

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1622: in test_aspect_chain_on_generator_no_return
    result = pytest.raises(StopIteration, gen.__next__)
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1609: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1609: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
_______________ test_aspect_chain_on_generator_no_return_advice ________________
tests/test_aspectlib.py:1639: in func
    raise StopIteration(a)
E   StopIteration: 3

The above exception was the direct cause of the following exception:
tests/test_aspectlib.py:1644: in test_aspect_chain_on_generator_no_return_advice
    result = pytest.raises(StopIteration, gen.__next__)
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1632: in foo
    yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1632: in foo
    yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib.py:1632: in foo
    yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
________________________ test_aspect_chain_on_generator ________________________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: 2

The above exception was the direct cause of the following exception:
tests/test_aspectlib_py3.py:24: in test_aspect_chain_on_generator
    result = pytest.raises(StopIteration, gen.__next__ if hasattr(gen, '__next__') else gen.next)
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:12: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:12: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
___________________ test_aspect_chain_on_generator_no_return ___________________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: None

The above exception was the direct cause of the following exception:
tests/test_aspectlib_py3.py:43: in test_aspect_chain_on_generator_no_return
    result = pytest.raises(StopIteration, gen.__next__)
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:31: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:31: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
__________________ test_aspect_chain_on_generator_yield_from ___________________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: 2

The above exception was the direct cause of the following exception:
tests/test_aspectlib_py3.py:72: in test_aspect_chain_on_generator_yield_from
    assert consume(func(0)) == 0
tests/test_aspectlib_py3.py:53: in consume
    list(it())
tests/test_aspectlib_py3.py:51: in it
    ret.append((yield from gen))
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:60: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:60: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
_____________ test_aspect_chain_on_generator_no_return_yield_from ______________
src/aspectlib/py3support.py:52: in advising_generator_wrapper_py3
    raise StopIteration(advice.value)
E   StopIteration: None

The above exception was the direct cause of the following exception:
tests/test_aspectlib_py3.py:89: in test_aspect_chain_on_generator_no_return_yield_from
    assert consume(func(0)) is None
tests/test_aspectlib_py3.py:53: in consume
    list(it())
tests/test_aspectlib_py3.py:51: in it
    ret.append((yield from gen))
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:78: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
tests/test_aspectlib_py3.py:78: in foo
    result = yield aspectlib.Proceed(arg + 1)
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
E   RuntimeError: generator raised StopIteration
_______________ test_story_empty_play_proxy_class_missing_report _______________
tests/test_aspectlib_test.py:392: in test_story_empty_play_proxy_class_missing_report
    assert replay.diff == """--- expected
E   AssertionError: assert '--- expected...  # returns\n' == '--- expected\...  # returns\n'
E     Skipping 224 identical leading characters in diff, use -v to show
E     - ror((123,))  # raises
E     + ror((123,),)  # raises
E     ?           +
E       +stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
E       +stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
E       +stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns...
E     
E     ...Full output truncated (16 lines hidden), use '-vv' to show
----------------------------- Captured stdout call -----------------------------
STORY/REPLAY DIFF:
    --- expected
    +++ actual
    @@ -0,0 +1,18 @@
    +stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
    +stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
    +stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns
    +stuff_1.raises(123) ** ValueError((123,))  # raises
    +stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
    +stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
    +stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
    +test_pkg1.test_pkg2.test_mod.target() == None  # returns
    +test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',))  # raises
    +stuff_2.raises(123) ** ValueError((123,))  # raises
    +that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)
    +that_long_stuf_1.mix(2) == (1, 2)  # returns
    +that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)
    +that_long_stuf_2.mix(4) == (3, 4)  # returns
    +that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)
    +that_long_stuf_3.mix() == (2,)  # returns
    +that_long_stuf_3.meth() == None  # returns
    +that_long_stuf_3.mix(10) == (2, 10)  # returns
ACTUAL:
    stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)
    stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns
    stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns
    stuff_1.raises(123) ** ValueError((123,))  # raises
    stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)
    stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns
    stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns
    test_pkg1.test_pkg2.test_mod.target() == None  # returns
    test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',))  # raises
    stuff_2.raises(123) ** ValueError((123,))  # raises
    that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)
    that_long_stuf_1.mix(2) == (1, 2)  # returns
    that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)
    that_long_stuf_2.mix(4) == (3, 4)  # returns
    that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)
    that_long_stuf_3.mix() == (2,)  # returns
    that_long_stuf_3.meth() == None  # returns
    that_long_stuf_3.mix(10) == (2, 10)  # returns

"--- expected\n+++ actual\n@@ -0,0 +1,18 @@\n+stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)\n+stuff_1.mix(3, 4) == (1, 2, 3, 4)  # returns\n+stuff_1.mix('a', 'b') == (1, 2, 'a', 'b')  # returns\n+stuff_1.raises(123) ** ValueError((123,))  # raises\n+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)\n+stuff_2.mix('a', 'b') == (0, 1, 'a', 'b')  # returns\n+stuff_2.mix(3, 4) == (0, 1, 3, 4)  # returns\n+test_pkg1.test_pkg2.test_mod.target() == None  # returns\n+test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',))  # raises\n+stuff_2.raises(123) ** ValueError((123,))  # raises\n+that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)\n+that_long_stuf_1.mix(2) == (1, 2)  # returns\n+that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)\n+that_long_stuf_2.mix(4) == (3, 4)  # returns\n+that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)\n+that_long_stuf_3.mix() == (2,)  # returns\n+that_long_stuf_3.meth() == None  # returns\n+that_long_stuf_3.mix(10) == (2, 10)  # returns\n"
_______________________________ test_with_class ________________________________
tests/test_contrib.py:106: in test_with_class
    assert logcap.messages == [
E   AssertionError: assert [('ERROR', '_... secs.'), ...] == [('ERROR', '__... secs.'), ...]
E     At index 2 diff: ('ERROR', 'action((Connection@6, 2, 2), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.') != ('INFO', 'connected!')
E     Right contains more items, first extra item: ('INFO', 'connected!')
E     Use -v to get the full diff
----------------------------- Captured stdout call -----------------------------
('ERROR', "__init__((Connection@1, 'to-something'), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.")
('ERROR', "__init__((Connection@2, 'to-something'), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.")
('ERROR', 'action((Connection@6, 2, 2), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.')
('ERROR', 'action((Connection@7, 2, 2), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.')
('ERROR', 'action((Connection@8, 2, 2), {}) raised exception Failed. 3 retries left. Sleeping 0 secs.')
('ERROR', 'action((Connection@12, 4, 4), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.')
('ERROR', 'action((Connection@13, 4, 4), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.')
('ERROR', 'action((Connection@14, 4, 4), {}) raised exception Failed. 3 retries left. Sleeping 0 secs.')
------------------------------ Captured log call -------------------------------
contrib.py                  57 ERROR    __init__((Connection@1, 'to-something'), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 78, in __init__
    self.__connect()
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    __init__((Connection@2, 'to-something'), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 78, in __init__
    self.__connect()
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@6, 2, 2), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@7, 2, 2), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 44, in retry_aspect
    cleanup(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@8, 2, 2), {}) raised exception Failed. 3 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 44, in retry_aspect
    cleanup(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@12, 4, 4), {}) raised exception Failed. 5 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@13, 4, 4), {}) raised exception Failed. 4 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 44, in retry_aspect
    cleanup(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
contrib.py                  57 ERROR    action((Connection@14, 4, 4), {}) raised exception Failed. 3 retries left. Sleeping 0 secs.
Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 45, in retry_aspect
    yield
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/__init__.py", line 265, in advising_function_wrapper
    result = cutpoint_function(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 91, in action
    raise OSError("Failed")
OSError: Failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/build/python-aspectlib/src/python-aspectlib/src/aspectlib/contrib.py", line 44, in retry_aspect
    cleanup(*args, **kwargs)
  File "/build/python-aspectlib/src/python-aspectlib/tests/test_contrib.py", line 83, in __connect
    raise OSError("Failed")
OSError: Failed
_______________________ test_decorate_asyncio_coroutine ________________________
tests/test_integrations_py3.py:24: in test_decorate_asyncio_coroutine
    loop.run_until_complete(coro())
/usr/lib/python3.7/asyncio/base_events.py:568: in run_until_complete
    return future.result()
/usr/lib/python3.7/asyncio/coroutines.py:123: in coro
    res = yield from res
src/aspectlib/py3support.py:41: in advising_generator_wrapper_py3
    advice = advisor.throw(*sys.exc_info())
src/aspectlib/debug.py:219: in advising_function
    res = yield
src/aspectlib/py3support.py:39: in advising_generator_wrapper_py3
    result = yield from gen
tests/test_integrations_py3.py:20: in coro
    yield from asyncio.sleep(0.01)
E   TypeError: cannot 'yield from' a coroutine object in a non-coroutine generator
------------------------------ Captured log call -------------------------------
debug.py                   217 CRITICAL coro()                                                        <<< aspectlib/py3support.py:30:advising_generator_wrapper_py3 < asyncio/coroutines.py:123:coro
debug.py                   224 CRITICAL coro ~ raised TypeError("cannot 'yield from' a coroutine object in a non-coroutine generator")
=========================== short test summary info ============================
FAIL docs/reference/aspectlib.rst::aspectlib.rst
FAIL src/aspectlib/debug.py::aspectlib.debug.log
FAIL src/aspectlib/test.py::aspectlib.test.LogCapture
FAIL src/aspectlib/test.py::aspectlib.test.Replay.unexpected
FAIL tests/test_aspectlib.py::test_aspect_on_generator_raise_stopiteration
FAIL tests/test_aspectlib.py::test_aspect_on_generator_result_from_aspect
FAIL tests/test_aspectlib.py::test_aspect_on_generator_result
FAIL tests/test_aspectlib.py::test_aspect_on_coroutine
FAIL tests/test_aspectlib.py::test_aspect_chain_on_generator
FAIL tests/test_aspectlib.py::test_aspect_chain_on_generator_no_return
FAIL tests/test_aspectlib.py::test_aspect_chain_on_generator_no_return_advice
FAIL tests/test_aspectlib_py3.py::test_aspect_chain_on_generator
FAIL tests/test_aspectlib_py3.py::test_aspect_chain_on_generator_no_return
FAIL tests/test_aspectlib_py3.py::test_aspect_chain_on_generator_yield_from
FAIL tests/test_aspectlib_py3.py::test_aspect_chain_on_generator_no_return_yield_from
FAIL tests/test_aspectlib_test.py::test_story_empty_play_proxy_class_missing_report
FAIL tests/test_contrib.py::test_with_class
FAIL tests/test_integrations_py3.py::test_decorate_asyncio_coroutine
SKIP [1] tests/test_aspectlib.py:460: condition: aspectlib.PY3
SKIP [1] tests/test_aspectlib.py:1193: condition: aspectlib.PY3
=============================== warnings summary ===============================
<undetermined location>
  [pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.
  pytest-capturelog plugin has been merged into the core, please remove it from your requirements.

tests/test_aspectlib.py::test_weave_wrong_module
  /build/python-aspectlib/src/python-aspectlib/src/aspectlib/test.py:203: UserWarning: Setting test_aspectlib.MissingGlobal to <class 'test_aspectlib.MissingGlobal'>. There was no previous definition, probably patching the wrong module.
    return self.__wrapped(*args, **kwargs)

-- Docs: http://doc.pytest.org/en/latest/warnings.html
========= 18 failed, 137 passed, 2 skipped, 3 warnings in 1.54 seconds =========

RFE: please start making github releases

Is it possible next time on release new version make the github release to have entry on RFE: please start making github releases? 🤔

I'm asking because only on make gh release is spread notification about new release to those who have set watch->releases.

More about gh releases is possible to find on
https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
https://github.com/marketplace/actions/github-release

aspectlib.debug.log doesn't play nice with weak references

import aspectlib
import aspectlib.debug
import weakref
import logging

logging.basicConfig()

class C(object):
    def m(self):
        pass

s = weakref.WeakSet()

aspectlib.weave(C, aspectlib.debug.log)
c = C()
s.add(c.m)

Trying to add c.m to the weakset results in the following exception:

TypeError: cannot create weak reference to '__logged__' object

Failing behavior with async methods

I was running this script test_script to print the name and time execution for each method inside my code. Here are the results: test_results

Look that for asynchronous methods, the lib prints some garbage information in result like await: <coroutine object create_columns_in_bulk at 0x7f76106a0ba0> or got result from await: <shannondb.protocol.ShannonDBClientProtocol object at 0x7f76106752e8> .Is it a misuse of library or aspectlib does not support async methods yet?

Here are the code for ShannonDBClient and columns module

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.