### Create test directories and file Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/testing.rst.txt Creates test directories and an empty file in the filesystem. This is the setup for the Story example that follows. ```python >>> os.makedirs('some/test/dir') >>> os.makedirs('some/test/empty') >>> with open('some/test/dir/file.txt', 'w') as fh: ... pass ``` -------------------------------- ### GNU tree clone with test setup and cleanup Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/testing.rst.txt Example of a simple GNU tree clone that prints a directory tree. It creates test directories and files, runs the tree function, and then cleans up with shutil.rmtree. This is used to illustrate the need for the Story testing tool. ```python >>> import os >>> def tree(root, prefix=''): ... if not prefix: ... print("%s%s" % (prefix, os.path.basename(root))) ... for pos, name in reversed(list(enumerate(sorted(os.listdir(root), reverse=True)))): ... print("%s%s%s" % (prefix, "|-- " if pos else "\-- ", name)) ... absname = os.path.join(root, name) ... if os.path.isdir(absname): ... tree(absname, prefix + ("| " if pos else " ")) ``` ```python >>> if not os.path.exists('some/test/dir'): os.makedirs('some/test/dir') >>> if not os.path.exists('some/test/empty'): os.makedirs('some/test/empty') >>> with open('some/test/dir/file.txt', 'w') as fh: ... pass ``` ```python >>> tree('some') some \-- test |-- dir | \-- file.txt \-- empty ``` ```python >>> import shutil >>> shutil.rmtree('some') ``` -------------------------------- ### Install aspectlib with easy_install Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/installation.rst.txt Legacy installation method for older environments. ```bash easy_install aspectlib ``` -------------------------------- ### Setup test directories Source: https://python-aspectlib.readthedocs.io/en/latest/testing.html Create temporary directories and files for testing the tree function. ```python >>> if not os.path.exists('some/test/dir'): os.makedirs('some/test/dir') >>> if not os.path.exists('some/test/empty'): os.makedirs('some/test/empty') >>> with open('some/test/dir/file.txt', 'w') as fh: ... pass ``` -------------------------------- ### Install aspectlib with pip Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/installation.rst.txt Standard installation method using the pip package manager. ```bash pip install aspectlib ``` -------------------------------- ### Replay story update example Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Shows how to update a story by pasting the output from the replay unexpected property. ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == None # returns ... mymod.badfunc() ** ValueError('boom!') # raises >>> with story.replay(): ... mymod.func('some arg') ... try: ... mymod.badfunc() ... except ValueError as exc: ... print(exc) boom! ``` -------------------------------- ### Story.replay(**options) Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Starts a replay transaction from a story instance. It returns a Replay object and accepts options for proxy, strict, and dump behavior. ```APIDOC ## Story.replay(**options) ### Description Starts a replay transaction from a story instance. It returns a Replay object and accepts options for proxy, strict, and dump behavior. ### Method `replay(**options)` ### Parameters - **proxy** (bool) - Optional - If True then unexpected uses are allowed (will use the real functions) but they are collected for later use. Default: True. - **strict** (bool) - Optional - If True then an AssertionError is raised when there were unexpected calls or there were missing calls (specified in the story but not called). Default: True. - **dump** (bool) - Optional - If True then the unexpected/missing calls will be printed (to sys.stdout). Default: True. ### Returns A aspectlib.test.Replay object. ### Example ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == 'some result' ... mymod.func('other arg') == 'other result' >>> with story.replay(strict=False): ... print(mymod.func('some arg')) ... mymod.func('bogus arg') some result Got bogus arg in the real code! STORY/REPLAY DIFF: --- expected... +++ actual... @@ -1,2 +1,2 @@ mymod.func('some arg') == 'some result' # returns -mymod.func('other arg') == 'other result' # returns +mymod.func('bogus arg') == None # returns ACTUAL: mymod.func('some arg') == 'some result' # returns mymod.func('bogus arg') == None # returns ``` ``` -------------------------------- ### Replay unexpected property usage example Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Demonstrates how to use the unexpected property to capture and print unexpected calls during a replay session. ```python >>> import mymod >>> with Story(mymod) as story: ... pass >>> with story.replay(strict=False, dump=False) as replay: ... mymod.func('some arg') ... try: ... mymod.badfunc() ... except ValueError as exc: ... print(exc) Got some arg in the real code! boom! >>> print(replay.unexpected) mymod.func('some arg') == None # returns mymod.badfunc() ** ValueError('boom!',) # raises ``` -------------------------------- ### Imports and module setup for aspectlib.test Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Imports standard library and aspectlib modules, defines namedtuples for call/result data, and sets up logging and sentinel values. ```python from collections import defaultdict from collections import namedtuple from difflib import unified_diff from functools import partial from functools import wraps from inspect import isclass from logging import getLevelName from logging import getLogger from sys import _getframe from traceback import format_stack from aspectlib import ALL_METHODS from aspectlib import mimic from aspectlib import weave from .utils import Sentinel from .utils import camelcase_to_underscores from .utils import container from .utils import logf from .utils import qualname from .utils import repr_ex try: from logging import _levelNames as nameToLevel except ImportError: from logging import _nameToLevel as nameToLevel try: from dummy_thread import allocate_lock except ImportError: from _dummy_thread import allocate_lock try: from collections import OrderedDict except ImportError: from .py2ordereddict import OrderedDict try: from collections import ChainMap except ImportError: from .py2chainmap import ChainMap __all__ = 'mock', 'record', "Story" logger = getLogger(__name__) logexception = logf(logger.exception) Call = namedtuple('Call', ('self', 'args', 'kwargs')) CallEx = namedtuple('CallEx', ('self', 'name', 'args', 'kwargs')) Result = namedtuple('Result', ('self', 'args', 'kwargs', 'result', 'exception')) ResultEx = namedtuple('ResultEx', ('self', 'name', 'args', 'kwargs', 'result', 'exception')) _INIT = Sentinel("INIT") ``` -------------------------------- ### Capture unexpected calls with Replay.unexpected Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.test.html Shows how to use Replay.unexpected to get a text representation of unexpected calls. The output can be used directly in a story. ```python >>> import mymod >>> with Story(mymod) as story: ... pass >>> with story.replay(strict=False, dump=False) as replay: ... mymod.func('some arg') ... try: ... mymod.badfunc() ... except ValueError as exc: ... print(exc) Got some arg in the real code! boom! >>> print(replay.unexpected) mymod.func('some arg') == None # returns mymod.badfunc() ** ValueError('boom!',) # raises ``` -------------------------------- ### Run tests in parallel Source: https://python-aspectlib.readthedocs.io/en/latest/contributing.html Run all test environments in parallel using detox. Requires installation via pip. ```bash detox ``` -------------------------------- ### Replaying a Story Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Example of replaying a defined story, showing how it handles expected calls and unexpected usage. ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == 'some result' ... mymod.func('other arg') == 'other result' >>> with story.replay(strict=False): ... print(mymod.func('some arg')) ... mymod.func('bogus arg') some result Got bogus arg in the real code! STORY/REPLAY DIFF: --- expected... +++ actual... @@ -1,2 +1,2 @@ mymod.func('some arg') == 'some result' # returns -mymod.func('other arg') == 'other result' # returns +mymod.func('bogus arg') == None # returns ACTUAL: mymod.func('some arg') == 'some result' # returns mymod.func('bogus arg') == None # returns ``` -------------------------------- ### Define a simple Client class for retry example Source: https://python-aspectlib.readthedocs.io/en/latest/examples.html Simple class used to show how to add retry functionality via aspectlib.weave. ```python class Client(object): def __init__(self, address): self.address = address self.connect() def connect(self): # establish connection def action(self, data): # do some stuff ``` -------------------------------- ### Define a production class for testing Source: https://python-aspectlib.readthedocs.io/en/latest/testing.html Defines a simple ProductionClass with a method that returns 'stuff'. Used as the target for the spy/mock examples. ```python >>> class ProductionClass(object): ... def method(self): ... return 'stuff' >>> real = ProductionClass() ``` -------------------------------- ### Initialize a StringIO with 'mystuff' and use 'aspectlib.weave' to mock open Source: https://python-aspectlib.readthedocs.io/en/latest/introduction.html Shows how to use 'aspectlib.weave' to temporarily replace the built-in 'open' function with a mock aspect. In the context manager, calling 'open' on a non-existent file returns 'mystuff' from a StringIO. The aspect returns 'StringIO("mystuff")' instead of a file object. ```python @aspectlib.Aspect def mock_open(): yield aspectlib.Return(StringIO("mystuff")) with aspectlib.weave(open, mock_open): assert open("/doesnt/exist.txt").read() == "mystuff" ``` -------------------------------- ### Run tests and checks with tox Source: https://python-aspectlib.readthedocs.io/en/latest/contributing.html Execute the full suite of tests, documentation builder, and spell checker using tox. ```bash tox ``` -------------------------------- ### Combine profilestats and logging on os.path.join Source: https://python-aspectlib.readthedocs.io/en/latest/examples.html Run os.path.join with both a profiler and a logging aspect to see both profiler stats and log output. ```python >>> import aspectlib.debug >>> with aspectlib.weave('os.path.join', [profilestats.profile(print_stats=10, dump_stats=True), aspectlib.debug.log(print_to=sys.stdout)]): ... print("os.path.join will be run with a profiler and aspectlib.debug.log:") ... os.path.join('a', 'b') ... os.path.join will be run with a profiler and aspectlib.debug.log: join('a', 'b') <<< ... ... function calls in ... seconds ... Ordered by: number of calls ... ncalls tottime percall cumtime percall filename:lineno(function) ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... ... 'a/b' ``` -------------------------------- ### Retry decorator successful execution example Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/contrib.html Demonstrates using the @retry decorator on a function that eventually succeeds after initial failures. ```python >>> should_fail = lambda foo=[1,2,3]: foo and foo.pop() >>> @retry ... def flaky_func(): ... if should_fail(): ... raise OSError('Tough luck!') ... print("Success!") ... >>> flaky_func() Success! ``` -------------------------------- ### Define Client Class Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/examples.rst.txt A sample Client class used to demonstrate aspect weaving. ```python class Client(object): def __init__(self, address): self.address = address self.connect() def connect(self): # establish connection def action(self, data): # do some stuff ``` -------------------------------- ### Retry decorator failure example Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/contrib.html Shows the behavior of the @retry decorator when a function consistently raises an exception and exceeds the retry limit. ```python >>> @retry ... def bad_func(): ... raise OSError('Tough luck!') ... >>> bad_func() Traceback (most recent call last): ... OSError: Tough luck! ``` -------------------------------- ### Define a test story with Story Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.test.html Illustrates how to use the Story context manager to define expected function calls and their corresponding results or exceptions. ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == 'some result' ... mymod.func('bad arg') ** ValueError("can't use this") ``` -------------------------------- ### Examine empty story output Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/testing.rst.txt Demonstrates creating an empty story for specified functions and observing the diff between expected and actual calls. ```python >>> from aspectlib.test import Story >>> with Story(['os.path.isdir', 'os.listdir']) as story: ... pass >>> with story.replay(strict=False) as replay: ... tree('some') some \-- test |-- dir | \-- file.txt \-- empty STORY/REPLAY DIFF: --- expected... +++ actual... @@ ... @@ +os.listdir('some') == ['test'] # returns +...isdir('some...test') == True # returns +os.listdir('some...test') == [...'empty'...] # returns +...isdir('some...test...dir') == True # returns +os.listdir('some...test...dir') == ['file.txt'] # returns +...isdir('some...test...dir...file.txt') == False # returns +...isdir('some...test...empty') == True # returns +os.listdir('some...test...empty') == [] # returns ACTUAL: os.listdir('some') == ['test'] # returns ...isdir('some...test') == True # returns os.listdir('some...test') == [...'empty'...] # returns ...isdir('some...test...dir') == True # returns os.listdir('some...test...dir') == ['file.txt'] # returns ...isdir('some...test...dir...file.txt') == False # returns ...isdir('some...test...empty') == True # returns os.listdir('some...test...empty') == [] # returns ``` -------------------------------- ### Replay a story with strict=False and handle unexpected calls Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.test.html Shows how to use Story to record expected calls, then replay with strict=False to allow unexpected calls. The replay prints the result for expected calls and reports a diff for unexpected ones. ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == 'some result' ... mymod.func('other arg') == 'other result' >>> with story.replay(strict=False): ... print(mymod.func('some arg')) ... mymod.func('bogus arg') some result Got bogus arg in the real code! STORY/REPLAY DIFF: --- expected... +++ actual... @@ -1,2 +1,2 @@ mymod.func('some arg') == 'some result' # returns -mymod.func('other arg') == 'other result' # returns +mymod.func('bogus arg') == None # returns ACTUAL: mymod.func('some arg') == 'some result' # returns mymod.func('bogus arg') == None # returns ``` -------------------------------- ### aspectlib.debug module source code Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/debug.html The complete source code for the aspectlib.debug module, including the log decorator and its helper functions. It defines frame_iterator, format_stack, strip_non_ascii, and the log decorator with its full docstring and usage examples. ```python import logging import os import string import sys from itertools import islice from aspectlib import Aspect from aspectlib import mimic try: from types import InstanceType except ImportError: InstanceType = type(None) logger = logging.getLogger(__name__) [docs]def frame_iterator(frame): """ Yields frames till there are no more. """ while frame: yield frame frame = frame.f_back [docs]def format_stack(skip=0, length=6, _sep=os.path.sep): """ Returns a one-line string with the current callstack. """ return ' < '.join("%s:%s:%s" % ( '/'.join(f.f_code.co_filename.split(_sep)[-2:]), f.f_lineno, f.f_code.co_name ) for f in islice(frame_iterator(sys._getframe(1 + skip)), length)) PRINTABLE = string.digits + string.ascii_letters + string.punctuation + ' ' ASCII_ONLY = ''.join(i if i in PRINTABLE else '.' for i in (chr(c) for c in range(256))) [docs]def strip_non_ascii(val): """ Convert to string (using `str`) and replace non-ascii characters with a dot (``.``). """ return str(val).translate(ASCII_ONLY) [docs]def log(func=None, stacktrace=10, stacktrace_align=60, attributes=(), module=True, call=True, call_args=True, call_args_repr=repr, result=True, exception=True, exception_repr=repr, result_repr=strip_non_ascii, use_logging='CRITICAL', print_to=None): """ Decorates `func` to have logging. Args func (function): Function to decorate. If missing log returns a partial which you can use as a decorator. stacktrace (int): Number of frames to show. stacktrace_align (int): Column to align the framelist to. attributes (list): List of instance attributes to show, in case the function is a instance method. module (bool): Show the module. call (bool): If ``True``, then show calls. If ``False`` only show the call details on exceptions (if ``exception`` is enabled) (default: ``True``) call_args (bool): If ``True``, then show call arguments. (default: ``True``) call_args_repr (bool): Function to convert one argument to a string. (default: ``repr``) result (bool): If ``True``, then show result. (default: ``True``) exception (bool): If ``True``, then show exceptions. (default: ``True``) exception_repr (function): Function to convert an exception to a string. (default: ``repr``) result_repr (function): Function to convert the result object to a string. (default: ``strip_non_ascii`` - like ``str`` but nonascii characters are replaced with dots.) use_logging (string): Emit log messages with the given loglevel. (default: ``"CRITICAL"``) print_to (fileobject): File object to write to, in case you don't want to use logging module. (default: ``None`` - printing is disabled) Returns: A decorator or a wrapper. Example:: >>> @log(print_to=sys.stdout) ... def a(weird=False): ... if weird: ... raise RuntimeError('BOOM!') >>> a() a() <<< ... a => None >>> try: ... a(weird=True) ... except Exception: ... pass # naughty code! a(weird=True) <<< ... a ~ raised RuntimeError('BOOM!',) You can conveniently use this to logs just errors, or just results, example:: >>> import aspectlib >>> with aspectlib.weave(float, log(call=False, result=False, print_to=sys.stdout)): ... try: ... float('invalid') ... except Exception as e: ... pass # naughty code! float('invalid') <<< ... float ~ raised ValueError(...float...invalid...) This makes debugging naughty code easier. PS: Without the weaving it looks like this:: >>> try: ... log(call=False, result=False, print_to=sys.stdout)(float)('invalid') ... except Exception: ... pass # naughty code! float('invalid') <<< ... float ~ raised ValueError(...float...invalid...) .. versionchanged:: 0.5.0 Renamed `arguments` to `call_args`. Renamed `arguments_repr` to `call_args_repr`. Added `call` option. """ ``` -------------------------------- ### Proceed Class Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Instruction for calling the decorated function. Can be used multiple times. If not used as an instance, the default args and kwargs are used. ```APIDOC ## Class: Proceed ### Description Instruction for calling the decorated function. Can be used multiple times. If not used as an instance then the default args and kwargs are used. ### Constructor #### `Proceed(*args, **kwargs)` - **args** - Optional - Arguments to be used when calling the decorated function. - **kwargs** - Optional - Keyword arguments to be used when calling the decorated function. ### Attributes - **args** - The positional arguments to be used. - **kwargs** - The keyword arguments to be used. ``` -------------------------------- ### Aspect decorator to log results Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/reference/aspectlib.rst.txt Defines an Aspect that logs the return value of the wrapped function. The advice uses yield to capture the result and prints it. The example shows the decorator definition and its usage with a generator function that returns a value via StopIteration, demonstrating that the aspect handles results correctly. ```python >>> from aspectlib import Aspect >>> @Aspect ... def log_results(*args, **kwargs): ... try: ... value = yield ... except Exception as exc: ... print("Raised %r for %s/%s" % (exc, args, kwargs)) ... raise ... else: ... print("Returned %r for %s/%s" % (value, args, kwargs)) ``` ```pycon >>> @log_results ... def weird_function(): ... yield 1 ... raise StopIteration('foobar') # in Python 3 it's the same as: return 'foobar' >>> list(weird_function()) Returned 'foobar' for ()/{} [1] ``` -------------------------------- ### Combine Profiling and Logging Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/examples.rst.txt Demonstrates mixing multiple aspects, specifically profiling and logging, on a single function. ```pycon >>> import aspectlib.debug >>> with aspectlib.weave('os.path.join', [profilestats.profile(print_stats=10, dump_stats=True), aspectlib.debug.log(print_to=sys.stdout)]): ... print("os.path.join will be run with a profiler and aspectlib.debug.log:") ... os.path.join('a', 'b') ... os.path.join will be run with a profiler and aspectlib.debug.log: join('a', 'b') <<< ... ... function calls in ... seconds ... Ordered by: cumulative time ... ncalls tottime percall cumtime percall filename:lineno(function) ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... ... 'a/b' ``` -------------------------------- ### Aspect decorator to log errors Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/reference/aspectlib.rst.txt Defines an Aspect that logs exceptions raised by the wrapped function. The advice uses try/yield to call the wrapped function and catches exceptions. The example shows the decorator definition and its usage with a broken generator and a broken function, demonstrating that the aspect correctly handles generators and coroutines. ```python >>> from aspectlib import Aspect >>> @Aspect ... def log_errors(*args, **kwargs): ... try: ... yield ... except Exception as exc: ... print("Raised %r for %s/%s" % (exc, args, kwargs)) ... raise ``` ```pycon >>> @log_errors ... def broken_generator(): ... yield 1 ... raise RuntimeError() >>> from pytest import raises >>> raises(RuntimeError, lambda: list(broken_generator())) Raised RuntimeError() for ()/{} ... >>> @log_errors ... def broken_function(): ... raise RuntimeError() >>> raises(RuntimeError, broken_function) Raised RuntimeError() for ()/{} ... ``` -------------------------------- ### Clone the repository Source: https://python-aspectlib.readthedocs.io/en/latest/contributing.html Clone your fork of the python-aspectlib repository locally to begin development. ```bash git clone git@github.com:your_name_here/python-aspectlib.git ``` -------------------------------- ### Create a development branch Source: https://python-aspectlib.readthedocs.io/en/latest/contributing.html Create a new branch for your specific bugfix or feature. ```bash git checkout -b name-of-your-bugfix-or-feature ``` -------------------------------- ### ObjectBag and BrokenBag implementation Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Utilities for tracking objects and a dummy bag implementation. ```python class ObjectBag(object): def __init__(self): self._objects = {} def has(self, obj): if id(obj) in self._objects: logdebug(' --- ObjectBag ALREADY HAS %r', obj) return True else: self._objects[id(obj)] = obj return False BrokenBag = type('BrokenBag', (), dict(has=lambda self, obj: False))() ``` -------------------------------- ### aspectlib.Proceed Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.html Instruction for calling the decorated function. Can be used multiple times. If not used as an instance then the default args and kwargs are used. ```APIDOC ## aspectlib.Proceed ### Description Instruction for calling the decorated function. Can be used multiple times. If not used as an instance then the default args and kwargs are used. ### Usage - `Proceed` or `None` - Calls the wrapped function with the default arguments. The yield returns the function's return value or raises an exception. Can be used multiple times (will call the function multiple times). - `Proceed(*args, **kwargs)` - Same as above but with different arguments. ### Parameters - `*args` (any) - Optional - Arguments to pass to the decorated function. - `**kwargs` (any) - Optional - Keyword arguments to pass to the decorated function. ### Notes - When used as an instance, the provided arguments are used; otherwise the default arguments are used. ``` -------------------------------- ### Record and replay story Source: https://python-aspectlib.readthedocs.io/en/latest/testing.html Record calls to os functions and replay them to verify behavior. ```python >>> from aspectlib.test import Story >>> with Story(['os.path.isdir', 'os.listdir']) as story: ... pass >>> with story.replay(strict=False) as replay: ... tree('some') some ackslash-- test |-- dir | ackslash-- file.txt ackslash-- empty STORY/REPLAY DIFF: --- expected... +++ actual... @@ ... @@ +os.listdir('some') == ['test'] # returns +...isdir('some...test') == True # returns +os.listdir('some...test') == [...'empty'...] # returns +...isdir('some...test...dir') == True # returns +os.listdir('some...test...dir') == ['file.txt'] # returns +...isdir('some...test...dir...file.txt') == False # returns +...isdir('some...test...empty') == True # returns +os.listdir('some...test...empty') == [] # returns ACTUAL: os.listdir('some') == ['test'] # returns ...isdir('some...test') == True # returns os.listdir('some...test') == [...'empty'...] # returns ...isdir('some...test...dir') == True # returns os.listdir('some...test...dir') == ['file.txt'] # returns ...isdir('some...test...dir...file.txt') == False # returns ...isdir('some...test...empty') == True # returns os.listdir('some...test...empty') == [] # returns ``` -------------------------------- ### Profile Function Calls Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/examples.rst.txt Uses profilestats to profile the os.path.join function. ```pycon >>> import os, sys, aspectlib, profilestats >>> with aspectlib.weave('os.path.join', profilestats.profile(print_stats=10, dump_stats=True)): ... print("os.path.join will be run with a profiler:") ... os.path.join('a', 'b') ... os.path.join will be run with a profiler: ... function calls in ... seconds ... Ordered by: cumulative time ... ncalls tottime percall cumtime percall filename:lineno(function) ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... 0.000 0.000 0.000 0.000 ... ... ... 'a...b' ``` -------------------------------- ### Commit and push changes Source: https://python-aspectlib.readthedocs.io/en/latest/contributing.html Stage, commit, and push your local changes to your GitHub fork. ```bash git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature ``` -------------------------------- ### Spy and mock with aspectlib.test.mock and record Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/testing.rst.txt Shows how to use aspectlib.test.mock and aspectlib.test.record to replace a method with a mock returning 3 and record calls. The patch is applied with weave, then rolled back with patch.rollback() to restore the original behavior. ```python >>> class ProductionClass(object): ... def method(self): ... return 'stuff' >>> real = ProductionClass() ``` ```python >>> from aspectlib import weave, test >>> patch = weave(real.method, [test.mock(3), test.record]) >>> real.method(3, 4, 5, key='value') 3 >>> assert real.method.calls == [(real, (3, 4, 5), {'key': 'value'})] ``` ```python >>> patch.rollback() >>> real.method() 'stuff' ``` -------------------------------- ### Replay.missing Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.test.html Returns a pretty text representation of just the missing calls. ```APIDOC ## Replay.missing ### Description Returns a pretty text representation of just the missing calls. ### Returns - **str** - A text representation of the missing calls. ``` -------------------------------- ### Log function calls with the log decorator Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.debug.html Demonstrates using the log decorator to print function call details, results, and exceptions to stdout. ```python >>> @log(print_to=sys.stdout) ... def a(weird=False): ... if weird: ... raise RuntimeError('BOOM!') >>> a() a() <<< ... a => None >>> try: ... a(weird=True) ... except Exception: ... pass # naughty code! a(weird=True) <<< ... a ~ raised RuntimeError('BOOM!',) ``` -------------------------------- ### Return Class Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Instruction for returning an optional value. If not used as an instance, None is returned. ```APIDOC ## Class: Return ### Description Instruction for returning a *optional* value. If not used as an instance then ``None`` is returned. ### Constructor #### `Return(value)` - **value** - Required - The value to be returned. ### Attributes - `value` - The value to be returned. ``` -------------------------------- ### Helper Utilities Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Internal helper functions for superclass discovery and module importing. ```python def _find_super_classes(klass): if hasattr(klass, '__mro__'): for k in klass.__mro__: yield k else: for base in klass.__bases__: yield base for k in _find_super_classes(base): yield k def _import_module(module): __import__(module) return sys.modules[module] ``` -------------------------------- ### Create an Aspect with aspectlib.Aspect Source: https://python-aspectlib.readthedocs.io/en/latest/_sources/introduction.rst.txt Demonstrates creating an aspect by decorating a generator to modify the return value of a function. ```python @aspectlib.Aspect def strip_return_value(*args, **kwargs): result = yield aspectlib.Proceed yield aspectlib.Return(result.strip()) @strip_return_value def read(name): return open(name).read() ``` -------------------------------- ### Use unexpected output to update the story Source: https://python-aspectlib.readthedocs.io/en/latest/reference/aspectlib.test.html Shows how to take the output from Replay.unexpected and paste it into the Story definition, then replay successfully. ```python >>> import mymod >>> with Story(mymod) as story: ... mymod.func('some arg') == None # returns ... mymod.badfunc() ** ValueError('boom!') # raises >>> with story.replay(): ... mymod.func('some arg') ... try: ... mymod.badfunc() ... except ValueError as exc: ... print(exc) boom! ``` -------------------------------- ### record function with options Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib/test.html Defines the `record` function that wraps a function with `_RecordingFunctionWrapper` or returns a partial for later application. Options include `calls`, `iscalled`, `callback`, and `extended`. ```python if func: return _RecordingFunctionWrapper( func, recurse_lock=recurse_lock_factory(), **options ) else: return partial(record, **options) ``` -------------------------------- ### Advising function wrapper implementation Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Internal logic for handling advising function wrappers, including generator-based advice processing and exception handling. ```python elif isinstance(advice, Return): raise StopIteration(advice.value) else: raise UnacceptableAdvice("Unknown advice %s" % advice) finally: advisor.close() return mimic(advising_generator_wrapper, cutpoint_function) else: def advising_function_wrapper(*args, **kwargs): if self.bind: advisor = self.advising_function(cutpoint_function, *args, **kwargs) else: advisor = self.advising_function(*args, **kwargs) if not isgenerator(advisor): raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) try: advice = next(advisor) while True: logdebug('Got advice %r from %s', advice, self.advising_function) if advice is Proceed or advice is None or isinstance(advice, Proceed): if isinstance(advice, Proceed): args = advice.args kwargs = advice.kwargs try: result = cutpoint_function(*args, **kwargs) except Exception: advice = advisor.throw(*sys.exc_info()) else: try: advice = advisor.send(result) except StopIteration: return result elif advice is Return: return elif isinstance(advice, Return): return advice.value else: raise UnacceptableAdvice("Unknown advice %s" % advice) finally: advisor.close() return mimic(advising_function_wrapper, cutpoint_function) ``` -------------------------------- ### Aspect class with bind=True Source: https://python-aspectlib.readthedocs.io/en/latest/_modules/aspectlib.html Shows how to use the bind=True option to access the cutpoint function within the advising function. ```python >>> @Aspect(bind=True) ... def my_decorator(cutpoint, *args, **kwargs): ... print("`%s` got called with args: %s kwargs: %s" % (cutpoint.__name__, args, kwargs)) ... result = yield ... print(" ... and the result is: %s" % (result,)) >>> @my_decorator ... def foo(a, b, c=1): ... print((a, b, c)) >>> foo(1, 2, c=3) `foo` got called with args: (1, 2) kwargs: {'c': 3} (1, 2, 3) ... and the result is: None ```