wrapt workshops

Back to Workshops

Guided JupyterLab workshops for wrapt: writing decorators, monkey patching, and object proxies.

The wrapt package itself is on GitHub at github.com/GrahamDumpleton/wrapt, with its source code, documentation and issue tracker.

Launch the workshops

Each option opens the whole collection in a new window, where JupyterLab shows the workshops in the order to take them.

  • Binder is a free public service needing no account. Sessions are temporary and can take a minute or two to start.
  • Codespaces needs a GitHub account and uses your Codespaces allowance. A codespace is kept until you delete it.

Decorators with wrapt

Guided JupyterLab workshops on writing decorators with wrapt: the wrapper signature, methods, arguments, state, and the decorators wrapt bundles, each beside the standard library version it replaces.

12 workshops About 2h 55m python wrapt decorators
1 Your first wrapt decorator 15m

Write the four argument wrapper that wrapt turns into a decorator, beside the closure version with functools.wraps. Ask the decorated function about itself and get the right answers with nothing copied, then see what the decorated name really is, and meet the instance argument.

2 What instance tells you 15m

One decorator on a plain function, an instance method, a class method, a static method and a class. Read what instance holds for each, see why args never contains self and why a call through the class looks the same, and learn the one ordering rule for classmethod.

methods
3 Arguments to the decorator 15m

Give a wrapt decorator settings of its own, three ways, each beside the standard library shape it replaces. A function that takes the settings and returns the decorator, a class whose __call__ is the wrapper, and a decorator that works with and without parentheses through wrapt.partial.

4 Handling the arguments 15m

What a wrapper does with the call's arguments. Why wrapt hands them over as a tuple and a dict rather than binding them to names, why args[0] is wrong, how a nested function binds the one argument you care about, how inspect.signature binds all of them by name with no special case for methods, and what a wrapper can do with the return value.

inspect
5 Keeping state 20m

Give a decorator state that outlives one call. Find out where an attribute set on a wrapt decorated function really lands, then build the state class wrapt's own examples use, reach its state through the decorated function with bind_state_to_wrapper, and give it optional arguments.

6 Switching a decorator off 10m

Use the enabled argument to wrapt.decorator to switch a decorator off. With a boolean the decision is made once, when the decorator is applied, and the original function is returned untouched; with a callable or a settings object it is made on every call, and the wrapper is bypassed.

7 One decorator for everything 15m

Write one decorator that does the right thing on a function, an instance method, a class method, a static method and a class, from the values of instance and wrapped, and have it refuse what it does not support. Then stack wrapt decorators with each other and with functools.wraps, and walk the chain with wrapper_chain and unwrapped.

8 Validating arguments 15m

Build two argument checkers as state classes. A TypeChecker compares each argument against its annotation, with the signature taken from the bound wrapped function on the first call so methods need no special case, and a ValueChecker takes constraint callables by parameter name. Apply both to functions and every kind of method, then find the one stacking order that reports errors properly.

9 Caching methods 15m

Put functools.lru_cache on a method and find the three problems that come with it, a shared budget, instances held alive by the cache, and a TypeError for anything unhashable. Then wrapt.lru_cache, which keeps a cache per instance, and what that means for cache_info, cache_clear and pickling.

10 Synchronising calls 15m

See a counter come up short under threads, then serialise the calls with wrapt.synchronized. Find where the lock lives for a function, an instance method, a class method, a static method and a class, share it with the context manager form, and supply a lock or semaphore of your own.

11 Wrapping async functions 15m

Put a synchronous timer on an async def and watch it time the creation of a coroutine rather than its run. Fix it with an async def wrapper, write one timer that serves def and async def alike, then use wrapt.synchronized on coroutines, where it switches to an asyncio.Lock, and see what its non-reentrancy asks of you.

asyncio
12 Changing the signature 10m

A decorator that supplies an argument the caller no longer passes leaves inspect.signature and help() describing a parameter nobody should pass. Fix what introspection sees with wrapt.with_signature, from a prototype function and from a factory that derives the new signature from the old, on a function and on a method, and stacked under another decorator.

Monkey patching with wrapt

Guided JupyterLab workshops on patching code you did not write with wrapt: every kind of method, taking a patch out again, temporary patches, getting there before the import, and wrapping what is not a function, each on a shipped package open beside the notebook.

10 workshops About 2h 40m python wrapt monkey-patching
1 Your first monkey patch 15m

Patch a function and a method by assignment, the way you already know, and see it work. Then do the same to a static method and a class method and watch what getattr handed you break both. Then wrapt.wrap_function_wrapper on all of them, each still its own kind of method, and meet the handle it returns.

2 Patching every kind of method 15m

One wrapper on every kind of target, an instance method, a class method, a static method, a dunder method and a method of a nested class, predicting what instance holds for each. Patch through a dotted path and a module named as a string, see what happens when the wrapper passes instance itself, and patch one object without touching the others.

3 Three ways to spell a patch 10m

The same patch three ways. wrap_function_wrapper as a call, from code that decides what to patch. patch_function_wrapper as a decorator on the wrapper, installing the patch when its module is imported, with enabled as a switch. function_wrapper turning a wrapper into a decorator you apply in place or hand to wrap_object. When each reads best.

4 Leaving things as you found them 20m

The handle a wrap function returns, and the lifecycle it unlocks. Ask whether a patch is still in place, walk the chain of wrappers on a target, and take a patch out with unwrap_object. Two patches removed in either order, a patch through a subclass removed with no residue, and the one arrangement removal refuses.

5 Patches that last a block 15m

Patches that remove themselves. unittest.mock.patch first, which replaces wholesale, then wrapt.scoped_function_wrapper for a with block, several at once and from a list with ExitStack, and transient_function_wrapper for a function call, read in two steps. What each does when something interferes, and where wrapture takes this next.

testing
6 Why your patch did nothing 15m

A patch applied correctly that changed nothing. A module that imported the function by name holds its own reference, and the patch on the original module never reaches it. See it in the shipped code, patch the alias too, and see why a method looked up through the class at call time is the safer target than a function, and a saved bound method is not.

7 Patching before the import 20m

A patch that waits for its module. The ? shortcut on the module name, a post import hook with when_imported that receives the module and fires at once for one already imported, and the string form that keeps the patch module itself unimported until the target is. Then the handle a deferred patch does not return, recovered with find_wrapper.

8 Wrapping what is not a function 15m

A patch on a dictionary a module reads its settings from. wrapt.wrap_object with a factory of your own, a BaseObjectProxy subclass that records which keys are read, and the least you need to know about proxies to write one. A callable proxy counting calls, arguments to the factory, and the two steps beneath every wrap function, resolve_path and apply_patch.

proxies
9 Patching instance attributes 15m

The value that lives on each object rather than on its class, set in __init__, which no wrap function so far can reach. wrapt.wrap_object_attribute installs a descriptor on the class that passes every read through a factory of yours, over a property, over a class default, stacked twice, and removed with its handle leaving the class as shipped.

proxies
10 Patching to observe 20m

The mechanisms put to work. A wrapper with state that counts and times a library's calls, a registry that installs each patch once and removes them all, a version check before patching, and deferral so the patches apply whether the library is imported before or after. The shape of every instrumentation agent, and where wrapture takes it.

instrumentation

Object proxies with wrapt

Guided JupyterLab workshops on standing in for an object with wrapt: what a proxy passes through and what it cannot, state of its own, intercepting special methods, the function wrapper beneath every decorator, and the callable, automatic, lazy and weak proxies wrapt ships, each beside the standard library way.

10 workshops About 2h 25m python wrapt proxies
1 Your first object proxy 15m

Write a delegating class by hand, a __getattr__ that forwards to the object it holds, and find what it gets wrong, from isinstance to where an assignment lands. Then wrap the same object in wrapt.BaseObjectProxy and see every one of those come right with nothing written, and meet __wrapped__ and the one question that still tells the two apart.

2 What does not pass through 15m

A proxy is a second object, so identity and type() tell the truth about it while __class__ and isinstance follow the target. An operator on a proxy returns a plain value, and an in-place operator keeps the proxy. Then the line wrapt.BaseObjectProxy draws on purpose, leaving __iter__ and __call__ off, and how a subclass that wraps something iterable defines __iter__ itself.

3 What belongs to the proxy 15m

An assignment through a proxy lands on the target, and unittest.mock.Mock keeps its own state by not being the target at all. The _self_ prefix for state that stays on the proxy, the two dictionaries a proxy has and __self_dict__ for reading its own, and __self_setattr__ in __init__ for an override that varies by instance, present only when the wrapped object has the method.

4 Intercepting special methods 15m

Set __getitem__ on a proxy instance and see nothing change, because Python looks special methods up on the type. Define it on a BaseObjectProxy subclass instead and record every key read from a settings dictionary while everything else passes through. Then __enter__ and __exit__ timing a lock, and __iadd__ on a list that stays a proxy.

5 Calling through a proxy 15m

A BaseObjectProxy over a function cannot be called, so wrapt.CallableObjectProxy adds __call__, and a subclass of it counts calls while still answering for the function to inspect, isinstance and __name__. Then wrapt.partial beside functools.partial, with the bound arguments removed from the signature both report and the function still underneath one of them.

6 Under the decorator 20m

Build a wrapt.FunctionWrapper by hand and see it is what @wrapt.decorator builds. Put one on an instance method, a class method and a static method, read the BoundFunctionWrapper that comes back through an instance with its _self_instance, _self_parent and _self_binding, and compare a descriptor written by hand. Then a wrapper pair of your own with __bound_function_wrapper__.

7 A proxy that fits its target 10m

A list, a function and a generator each wrapped in BaseObjectProxy and refusing to iterate, be called or be resumed, then each wrapped in AutoObjectProxy and working, because it reads its target at construction and adds the special methods that target has. The cost, a class generated per instance, measured, and when each base is the right one.

8 Wrapping what does not exist yet 15m

A wrapt.LazyObjectProxy with a callback in place of a target, run once on first use and never at construction. wrapt.lazy_import over a shipped module that prints when it is imported, with sys.modules checked before and after, one attribute of a module, and the interface hint that lets a lazy proxy be callable before the import has happened.

9 Holding a function weakly 10m

weakref.ref on a bound method is dead at once and weakref.proxy on one raises, because a bound method is made on each access and discarded. wrapt.WeakFunctionProxy holds the instance and the function weakly and rebinds on each call, raises ReferenceError once the instance is collected and runs a callback when it goes, which builds a registry of callbacks that lets its objects go.

10 Saving and restoring a proxy 15m

pickle.dumps, copy.copy and copy.deepcopy each refuse a proxy with NotImplementedError, because the base class cannot know what state a subclass added and declines to guess. A proxy over a dictionary with a label of its own defines __reduce__ and round trips through pickle with both intact, then __copy__ and __deepcopy__ the same way, and dill needs nothing more.