DEV Community

Cover image for Making Python Modules Callable: Introducing Cadule
Maple
Maple

Posted on

Making Python Modules Callable: Introducing Cadule

When writing Python code, I often find myself missing a feature from Node.js: the ability to directly require a module and have it become callable. In Node.js, you can export a function from a module and call it immediately:

const messUpThings = require('./mess-up-things');
messUpThings(); // Works!
Enter fullscreen mode Exit fullscreen mode

I know some developers aren't fond of this pattern, but there are legitimate use cases for it.

The Problem

Consider a scenario where you need to write a helper function with a self-descriptive name, like mess_up_things. The function name itself tells you what it does. You have a few options:

  1. Create a util or helper module: Put it in a util.py or helper.py module. But many developers dislike these generic names, especially Go developers who prefer more descriptive package names.

  2. Create a dedicated file: Put it in mess_up_things.py. But then you need to write verbose import statements like:

from mess_up_things import mess_up_things
Enter fullscreen mode Exit fullscreen mode

This repetition feels unnecessary. Wouldn't it be nice if you could just do:

import mess_up_things
mess_up_things()
Enter fullscreen mode Exit fullscreen mode

Exploring Solutions

If you're familiar with Python's magic methods, you might know that any object with a __call__ method becomes callable. Since modules are instances of types.ModuleType, and they support magic methods like __getattr__, couldn't we just define a __call__ method directly on a module?

Unfortunately, Python doesn't support this out of the box. There was actually PEP 713 that proposed adding module-level __call__ support, but it was rejected.

However, if you're familiar with Python's runtime, you'll find there's a trick to achieve this behavior:

import sys

class MyModule(sys.modules[__name__].__class__):
    def __call__(self):
        print("Messing up things...")

sys.modules[__name__].__class__ = MyModule
Enter fullscreen mode Exit fullscreen mode

This works by dynamically replacing the module's __class__ at runtime. But who wants to copy-paste this boilerplate every time?

Enter Cadule

To avoid repeating this boilerplate, I've encapsulated this simple mechanism into a package called Cadule (short for Callable [Mo]dule Less).

Installation

pip install cadule
Enter fullscreen mode Exit fullscreen mode

Usage

Create a file called mess_up_things.py:

import cadule

@cadule
def __call__():
    print("Messing up things...")
Enter fullscreen mode Exit fullscreen mode

Then in your Python REPL or another script:

>>> import mess_up_things
>>> mess_up_things()
Messing up things...
>>> callable(mess_up_things)
True
Enter fullscreen mode Exit fullscreen mode

That's it! The entire mess_up_things module is now a callable object. When you call it, it executes the decorated __call__ function.

You can also pass arguments and return values:

import cadule

@cadule
def __call__(target):
    return f"Messing up {target}!"
Enter fullscreen mode Exit fullscreen mode
>>> import mess_up_things
>>> mess_up_things("the database")
'Messing up the database!'
Enter fullscreen mode Exit fullscreen mode

When to Use It

Cadule is particularly useful for:

  • Single-purpose modules: When a module's main purpose is to expose one function
  • DSL and fluent interfaces: Creating more natural-looking APIs
  • Scripts and utilities: Making command-line tools more intuitive

Github: https://github.com/aisk/cadule

Top comments (0)