A Python library for enhancing existing modules without modifying their source code - a clean alternative to forking, vendoring, and monkey-patching.
modshim allows you to overlay custom functionality onto existing Python modules while preserving their original behavior. This is particularly useful when you need to:
- Fix bugs in third-party libraries without forking
- Modify the behavior of existing functions
- Add new features or options to existing classes
- Test alternative implementations in an isolated way
It works by creating a new, "shimmed" module that combines the original code with your enhancements, without modifying the original module.
Suppose we want to enhance the standard library's textwrap module. Our goal is to add a prefix argument to TextWrapper to prepend a string to every wrapped line.
First, create a Python module containing your modifications. It should mirror the structure of the original textwrap module, redefining only the parts we want to modify. For classes like TextWrapper, this can be done by creating a subclass with the same name as the original class:
Next, use modshim to mount your modifications over the original textwrap module, creating a new, combined module.
Now, you can import from super_textwrap. Notice how we can call the original wrap() convenience function, but pass our new prefix argument. This works because our modifications are overlaid on the original textwrap module, so the merged super_textwrap module uses our enhanced TextWrapper class inside the wrap function instead of the original.
Crucially, the original textwrap module remains completely unchanged. This is the key advantage over monkey-patching.
You can create packages that automatically apply a shim to another module, making your enhancements available just by importing your package.
This is done by calling shim() from within your package's code and using your own package's name as the mount point, so your package gets "replaced" with new merged module.
To adapt our textwrap example, we could create a super_textwrap.py file like this:
Now, anyone can use your enhanced version simply by importing your package:
Let's tackle a more complex, real-world scenario. We want to add a robust, configurable retry mechanism to requests for handling transient network issues or server errors. The standard way to do this in requests is by creating a Session object and mounting a custom HTTPAdapter.
With modshim, we can create an enhanced Session class that automatically configures retries, and then overlay it onto the original requests library. To do this correctly, our enhancement package (requests_extra) must mirror the structure of the original requests package.
We'll create a package named requests_extra with two files.
requests_extra/__init__.py
This is the entry point to our package. It contains the magic shim call.
requests_extra/sessions.py
This file matches the location of the Session class in the original requests library (requests/sessions.py). Here, we define our enhanced Session with the new functionality, by subclassing the original Session implementation:
Because our enhanced Session class now enables retries by default, we don't even need to instantiate it directly. modshim's AST rewriting ensures that internal references within the requests module are updated. This means convenience functions like requests.get() will automatically use our enhanced Session class:
When you run this code, you will see urllib3's log messages showing the retries in action, followed by the final caught exception:
- Internal Reference Rewriting: This example demonstrates modshim's most powerful feature. By replacing requests.sessions.Session, we automatically upgraded top-level functions like requests.get() because their internal references to Session are redirected to our new class.
- Preservation of the Original Module: The original requests package is not altered. Code in other parts of an application that imports requests directly will continue to use the original Session object without any retry logic, preventing unintended side-effects.
modshim creates virtual merged modules by intercepting Python's import system. At its core, modshim works by installing a custom import finder (ModShimFinder) into sys.meta_path.
When you call shim(), it registers a mapping between three module names: the "lower" (original) module, the "upper" (enhancement) module, and the "mount" point (the name under which the combined module will be accessible).
When the mounted module is imported, the finder:
- Locates both the lower and upper modules using Python's standard import machinery.
- Creates a new virtual module at the mount point.
- Executes the lower module's code first, establishing the base functionality.
- Executes the upper module's code, which can override or extend the lower module's attributes.
- Handles imports within these modules by rewriting their ASTs (Abstract Syntax Trees) to redirect internal references to the new mount point.
This AST transformation is key. It ensures that when code in either module imports from its own package (e.g., a relative import), those imports are redirected to the new, combined module. This maintains consistency and prevents circular import issues.
The system is thread-safe, handles sub-modules recursively, and supports bytecode caching for performance. All of this happens without modifying any source code on disk.
Monkey-patching involves altering a module or class at runtime. For example, you might replace textwrap.TextWrapper with your custom class.
This approach has major drawbacks:
- Global Pollution: It alters the textwrap module for the entire application. Every part of your code, including third-party libraries, will now unknowingly use your modified version. This can lead to unpredictable behavior and hard-to-find bugs.
- Fragility: Patches can easily break when the underlying library is updated.
- Poor Readability: It's hard to track where modifications are applied and what version of a class or function is actually being used.
modshim avoids these problems by creating a new, separate, and isolated module. The original textwrap is never touched. You explicitly import from your mount point (super_textwrap) when you want the enhanced functionality. This provides clear, predictable, and maintainable code.
Forking a project means creating your own copy of its entire codebase. While this gives you total control, it comes with significant downsides:
- Maintenance Overhead: You become the maintainer of the forked library. When the original project releases updates, bug fixes, or security patches, you have to manually merge them into your fork. This can be time-consuming and complex.
- Divergence: Over time, your fork can drift away from the original, making it increasingly difficult to incorporate upstream changes.
- Dependency Complexity: Your project now depends on your custom fork, not the official version. This complicates deployment and can be confusing for other developers who might not realize they're using a non-standard version of the library.
- Bridging Contribution Gaps: Contributing changes upstream is ideal, but open-source maintainers may be slow or reluctant to merge them due to differing priorities, project vision, or contribution backlogs. Forking in response creates a permanent schism and a significant maintenance burden for what might be a small change.
modshim offers a more lightweight and strategic alternative. It allows you to apply necessary changes immediately for your own project's needs without waiting for upstream review. Your enhancements live in a small, separate package that depends on the official upstream library. This keeps your changes cleanly isolated, eliminates the maintenance burden of a full fork, and makes it easy to continue tracking upstream updates. If you still intend to contribute your changes, modshim keeps your patch separate and ready for a future pull request.
Unlike vendoring (copying) third-party code into your project:
- No need to maintain copies of dependencies.
- It's easier to update the underlying library.
- It creates a cleaner separation between original and custom code.
- Your enhancement code is more maintainable and testable.
.png)


