Venusian
Venusian is a Python library that enables framework authors to defer actions typically performed by decorators at import time. Instead, these actions are executed during a separate 'scan' phase. This allows for more flexible configuration and improved testability of decorated code. The library is actively maintained, with the current version being 3.1.1, and supports CPython and PyPy versions 3.7 and above.
Warnings
- breaking Versions 3.0.0 and 2.0.0 dropped support for Python 2.7, 3.3, and 3.4. Projects on these older Python versions must remain on `venusian < 2.0.0` or `venusian < 3.0.0` respectively (for Python 3.x).
- breaking Version 3.1.0 removed support for Python 3.5 and 3.6. Users on these Python versions should use `venusian < 3.1.0` if they cannot upgrade their Python interpreter.
- gotcha By default, `venusian.Scanner.scan()` will propagate `ImportError` exceptions encountered while trying to import modules. To suppress or handle these errors, provide an `onerror` callback function to the `scan()` method.
- gotcha Venusian decorators attached to a class are not implicitly inherited by subclasses. Each class declaration should have its own local set of callbacks; callbacks added via decorations are not inherited from a superclass.
Install
-
pip install venusian
Imports
- Scanner
from venusian import Scanner
- attach
venusian.attach(wrapped, callback)
Quickstart
import venusian
# Define a custom decorator that uses venusian.attach
class mydecorator:
def __init__(self, category='default'):
self.category = category
def __call__(self, wrapped):
def callback(scanner, name, obj):
# This function is called during the scan phase
print(f"Scanning {obj.__name__} in category '{self.category}'")
# You might register `obj` or `name` in a registry here
scanner.registry.append((name, obj, self.category))
venusian.attach(wrapped, callback, category=self.category)
return wrapped # Return the original object, untouched at import time
# Define some functions to be decorated
@mydecorator(category='views')
def my_view():
return "Hello, Venusian!"
@mydecorator(category='api')
def my_api_endpoint():
return {"data": "from api"}
# Create a scanner instance and a custom registry
scanner = venusian.Scanner(registry=[])
# Scan the current module (or a specific package/module)
# The callbacks attached via mydecorator will now be executed
scanner.scan(__name__)
# Access the collected information from the registry
print("\n--- Collected Registry Data ---")
for name, obj, category in scanner.registry:
print(f"Name: {name}, Object: {obj.__name__}, Category: {category}")
# Verify that original functions are callable
print(f"\nDirect call to my_view: {my_view()}")