Library / SDK
GrahamDumpleton/wrapt avatar
GrahamDumpleton/wrapt

wrapt: the decorator library that refuses to hide the function

A Python module for decorators, wrappers and monkey patching.

2,308 stars256 forksPythonBSD-2-Clause

At a glance

What is it?
A transparent object proxy for Python that keeps signatures, annotations and introspection intact through wrapping, with a C extension for speed and a pure Python fallback when nothing can be compiled.
Who is it for?
wrapt earns its place in code that other tools inspect: libraries that build decorators for frameworks, testing harnesses, and instrumentation that has to keep the wrapped signature readable. Plain `functools.wraps()` gets you most of the way, but not the object proxy behaviour, the instance-aware call signature or the binding rules that make a decorator work identically on a function, a method and a class.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A pass-through decorator that shows the whole call shape

The quick start is worth reading closely because it explains the library's central idea. A wrapt decorator is not a function that takes a function and returns a wrapper. It is a function of four arguments, and the one that matters most is `instance`:

python
import wrapt

@wrapt.decorator
def pass_through(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)

@pass_through
def function():
    pass

The difference from a normal decorator shows up the moment you decorate something other than a plain function. When the decorated object is an ordinary function, `instance` is None. When it is an instance method, `instance` is the object the method was called on. When it is a classmethod or a staticmethod, the meaning shifts again. The decorator receives all of that and can decide what to do, which is why a single wrapt decorator can be applied to a function, a method, a classmethod, a staticmethod or a class without rewriting itself per case.

The README makes this concrete with a decorator that inspects `instance` and the wrapped object to report what kind of thing it was attached to, using `inspect.isclass()` on either the wrapped object or the instance to tell a class from a function from a classmethod. That example is short, but it is the whole point of the library in fifteen lines.

Passing arguments to a decorator without losing the wrapper

Adding arguments to a wrapt decorator is a two-level structure, and the outer function is an ordinary Python closure that returns the actual decorator:

python
import wrapt

def with_arguments(myarg1, myarg2):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print(f"Arguments: {myarg1}, {myarg2}")
        return wrapped(*args, **kwargs)
    return wrapper

@with_arguments(1, 2)
def function():
    pass

Compared with the classic nested-closure decorator, the difference is what the outer function receives at decoration time. Here `wrapper` is created by `wrapt.decorator`, so it is already a proper wrapper object rather than a plain closure, and the arguments arrive as `myarg1` and `myarg2` captured by the enclosing scope instead of being smuggled through the wrapped function's own signature.

That distinction matters more than it looks for anything that inspects the result later. A plain closure reports the signature of `wrapper`, not of the function it wraps, and its `__wrapped__` chain and annotations are whatever the closure happened to carry. A wrapt wrapper reports the wrapped function's signature. This is the reason a library built on wrapt can be re-wrapped by another library built on wrapt and still introspect correctly.

The object proxy underneath, and why it is not just a decorator helper

The README describes wrapt as providing a transparent object proxy for Python that can be used as the basis for constructing function wrappers and decorator functions. The decorators are the common use, but the proxy is the underlying mechanism, and it is exposed for people who need to wrap objects that are not functions at all.

Transparency is the claim that needs scrutiny. The intent is that attribute access, introspection and the type of the original object all appear unchanged to code that only knows about the proxy. That is what lets an attribute lookup, a signature inspection and a class check return what the caller expects even though a proxy sits in between. It is also why the library is described as going beyond mechanisms such as `functools.wraps()`, which copies a handful of attributes onto a new function and leaves the object itself different.

The README groups the capabilities as universal decorators that work with functions, methods, classmethods, staticmethods and classes, transparent object proxies for advanced wrapping scenarios, monkey patching utilities for runtime modifications, and a long list of introspection preservation items covering signatures and annotations. It also claims thread-safe decorator implementations. Treat that last one as a design property of the recommended patterns rather than as permission to mutate shared state inside your decorator.

For the monkey patching side, the documentation is on Read the Docs rather than in the README, and the README's own guidance is that if monkey patching is what brought you here, there is a sibling project worth looking at. `wrapture` is built on the same machinery and gives a higher level API over `wrap_object()`: point at a method by name, stub it, fail it, transform its arguments or result, or wrap it with a decorator, then remove it again. `autowrapt` is the other related project, and it triggers `wrapt.discover_post_import_hooks()` at interpreter startup so patches can be applied to an application you did not modify.

A C extension with a pure Python fallback and how it is decided

Performance is handled by compiling the parts that need it. The README states that a C extension module is used for performance critical components, and that an automatic fallback to a pure Python implementation is provided for target systems with no compiler available.

The install script shows exactly how that choice is made, and the logic is readable in `setup.py`. It looks at two environment variables in order, `WRAPT_INSTALL_EXTENSIONS` and then `WRAPT_EXTENSIONS`, and interprets the value: the string `false` disables the extension, `true` forces it. If neither variable is set, extensions are enabled by default. Then there is an unconditional override: if the Python implementation is not CPython, the extension is disabled, because the C module is written against CPython internals. The extension is registered with `optional=True` unless extensions were forced, which is what lets a build fall back silently rather than fail outright.

So there are three distinct controls. Platform decides by itself on PyPy. A packager or a locked down build environment can opt out with an environment variable. Someone debugging a compilation problem can opt in with `true` to make the failure loud instead of silent. The project supports Python 3.9 and up on both CPython and PyPy, and the packaging metadata lists every version from 3.9 to 3.15 with a Production/Stable development status.

One packaging detail is easy to miss. The project explicitly lists its packages as `wrapt` and `wrapt-stubs` rather than letting setuptools discover them, because the stub-only companion package has a hyphen in its directory name and no `__init__.py`, which the default discovery does not pick up. If you write type checking against wrapt-decorated functions, those stubs are the reason the types resolve, and they ship in the same distribution.

Learning path: three workshop sets and a documentation boundary

The README is unusually generous about teaching material, and it is worth knowing where the line between repository and external docs falls. Everything on decorators, proxies and monkey patching itself lives on wrapt.readthedocs.io, and the README links there rather than duplicating it.

What the README does add is two sets of hands-on workshops that run in JupyterLab and check your work as you go, with nothing to install locally. The first set, in the decorator-workshops repository, teaches Python decorators using only the standard library, from what a decorator is through to writing your own for functions, methods, classes and coroutines. The README is explicit that this is the place to start if decorators are new to you, and that the wrapt workshops build on it. That ordering is the useful part: wrapt assumes you already know what you are wrapping and why.

The second set, in the wrapt-workshops repository, covers wrapt itself in three collections: writing decorators with wrapt, monkey patching with wrapt, and object proxies with wrapt. Each is shown beside the standard library way of doing the same thing, which is the comparison that teaches the most, since the failure modes of `functools.wraps()` are easiest to see when they sit next to the working alternative.

Both sets launch from a browser through JupyterLite, from Binder, or in GitHub Codespaces, and the repository READMEs explain how to run them locally. The licence is BSD-2-Clause, with the copyright held by Graham Dumpleton, and the project classifies itself as Production/Stable. Version 2.5.0 was published on 2026-09-27, with release candidates for 2.5.0 and 2.4.2 in the days before it, and the default branch is `develop`.

Editorial conclusion

wrapt earns its place in code that other tools inspect: libraries that build decorators for frameworks, testing harnesses, and instrumentation that has to keep the wrapped signature readable. Plain `functools.wraps()` gets you most of the way, but not the object proxy behaviour, the instance-aware call signature or the binding rules that make a decorator work identically on a function, a method and a class. The trade is that wrapt is a real dependency with a C extension, so pip installs may compile, and the pure Python path is slower. Start with the `pip install wrapt` route and the pass-through example in the README, then read the workshops on wrapt.readthedocs.io before writing your first argument taking decorator.

Frequently asked questions

What is wrapt used for in Python?

wrapt is a Python module for decorators, wrappers and monkey patching, built on a transparent object proxy. Its purpose is to keep decorators introspectable: signatures, annotations and the type of the decorated object survive wrapping, so tools that inspect functions still see what they expect.

How do I install wrapt?

Run `pip install wrapt`. The package supports Python 3.9 and up on CPython and PyPy, and requires-python is declared as 3.9 or higher. There is also a `Development Status :: 5 - Production/Stable` classifier in the packaging metadata.

Does wrapt need a compiler to install?

No. A C extension is built for performance critical parts when possible, and an automatic pure Python fallback is used where no compiler is available. The `WRAPT_INSTALL_EXTENSIONS` and `WRAPT_EXTENSIONS` environment variables override the default, with `false` disabling the extension and `true` forcing it, and extensions are disabled automatically on implementations other than CPython.

Official sources

  1. GrahamDumpleton/wrapt on GitHub
  2. Issues
  3. License: BSD-2-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/grahamdumpleton-wrapt.svg)](https://hysenlabs.com/projects/grahamdumpleton-wrapt)