Runtime dynamic code injection and manipulation via decorators
Project description
injects
Runtime dynamic code injection and manipulation via decorators
Core Concept
injects is a metaprogramming framework that enables dynamic code transformation at runtime through a composable decorator system. Unlike traditional decorators that simply wrap functions, injects modifies the actual implementation of decorated functions through AST manipulation.
Core Design Philosophy
- Minimal Core: Focus on providing essential transformation capabilities
- Composable: Build complex transformations from simple components
- Extensible: Easy to extend with custom patterns, conditions, and transformers
- Explicit: Clear about what is being transformed and how
Installation
pip install injects
Basic Usage
import injects
from injects.patterns.function import FunctionCallPattern
from injects.insert import before
# Define a transformation that logs function calls
def log_call(node):
"""Create a log statement for function calls"""
import ast
# Extract function name from call node
if isinstance(node.func, ast.Name):
func_name = node.func.id
elif isinstance(node.func, ast.Attribute):
func_name = f"{ast.unparse(node.func.value)}.{node.func.attr}"
else:
func_name = "unknown"
# Create a print statement
return ast.parse(f"print(f'Calling {func_name}')").body[0]
# Apply to a function - logs all database function calls
@injects.build(
log_call,
pattern=FunctionCallPattern("db_.*"),
position=before
)
def get_user(user_id):
"""Get user data from database"""
user = db_fetch_user(user_id)
return db_enrich_user(user)
# Result: logs "Calling db_fetch_user" and "Calling db_enrich_user" when executed
Creating Composable Transformers
The real power of injects comes from its composability. Create reusable transformers and combine them:
import injects
from injects.patterns.function import FunctionCallPattern, ReturnPattern
from injects.insert import before, after, around
from injects.conditionals import InstanceOf
import ast
# Create reusable transformers
# 1. Log database calls
db_logger = injects.build(
log_call, # Reuse the transformer from above
pattern=FunctionCallPattern("db_.*"),
position=before
)
# 2. Validate return values aren't None
def validate_return(node):
"""Add validation to ensure return values aren't None"""
validation = ast.parse("""
if result is None:
raise ValueError("Function returned None unexpectedly")
""").body
return validation
return_validator = injects.build(
validate_return,
pattern=ReturnPattern(),
position=before
)
# 3. Time function execution
def add_timing(node):
"""Add timing code around function execution"""
start_time = ast.parse("import time\nstart_time = time.time()").body
end_time = ast.parse("""
end_time = time.time()
print(f"Function executed in {end_time - start_time:.4f} seconds")
""").body
return start_time, end_time # for 'around' position, return (before, after)
function_timer = injects.build(
add_timing,
pattern=InstanceOf(ast.FunctionDef),
position=around
)
# Apply multiple transformers to a function
@db_logger
@return_validator
@function_timer
def process_user_data(user_id):
user = db_fetch_user(user_id)
processed = transform_user_data(user)
db_save_user(processed)
return processed
Pattern Shortcuts
Create intuitive pattern matchers with less code:
from injects.patterns.function import FunctionCallPattern
from injects.patterns.variable import VariableAssignmentPattern
import re
# Match specific function calls
db_calls = FunctionCallPattern("db_.*")
api_calls = FunctionCallPattern("api_.*")
# Combine patterns
data_calls = db_calls | api_calls
# Create patterns for variable assignments
temp_vars = VariableAssignmentPattern("temp_.*")
result_vars = VariableAssignmentPattern("result.*")
# Match any call to print or logging functions
log_pattern = FunctionCallPattern("print") | FunctionCallPattern("log.*")
Conditional Transformations
Apply transformations based on specific conditions:
from injects.conditionals import InstanceOf, AttributeValue, HasAttribute
import ast
# Only apply to specific AST node types
name_nodes = InstanceOf(ast.Name)
call_nodes = InstanceOf(ast.Call)
# Check for specific attribute values
db_name = AttributeValue("id", "db", ast.Name)
# Check if an attribute exists
has_value = HasAttribute("value")
# Combine conditions with logical operators
complex_condition = name_nodes & ~db_name | (call_nodes & has_value)
# Apply a transformation with the condition
@injects.build(
transformer=log_call,
pattern=FunctionCallPattern(),
condition=complex_condition,
position=before
)
def process_data():
data = db.fetch_all()
return transform(data)
Creating Custom Injection Points
The insertion system provides full control over how code is injected:
from injects.insert import before, after, replace, around
# Insert logging before function calls
@injects.build(log_call, pattern=FunctionCallPattern(), position=before)
# Add validation after function calls
@injects.build(validate_result, pattern=FunctionCallPattern(), position=after)
# Replace problematic function calls with safe alternatives
@injects.build(make_safe, pattern=FunctionCallPattern("unsafe_.*"), position=replace)
# Add try/except handling around database operations
@injects.build(add_error_handling, pattern=FunctionCallPattern("db_.*"), position=around)
The Injector Pattern
Turn any function into a reusable transformer with the injector decorator:
import injects
from injects.patterns.function import FunctionCallPattern
from injects.insert import before
# Create a reusable transformer for logging
@injects.injector(
pattern=FunctionCallPattern(),
position=before
)
def debug_log(node):
"""Log function calls with full detail for debugging"""
import ast
func_name = ast.unparse(node.func)
args_repr = ", ".join(ast.unparse(arg) for arg in node.args)
return ast.parse(f"print(f'DEBUG: Calling {func_name}({args_repr})')").body[0]
# Now use debug_log as a decorator
@debug_log
def important_function():
result = calculate_value()
return process_result(result)
Core Components
The library is built around four key components:
- Transformer Functions: Define what transformation to apply
- Pattern Matchers: Specify where to apply transformations
- Condition Checkers: Control when transformations are applied
- Position Specifiers: Determine how transformations are injected
Extension System
The core functionality can be extended with specialized modules:
- injects.patterns: Common code patterns to match
- injects.conditionals: Predefined conditions for transformations
- injects.transforms: Ready-to-use transformation functions
- injects.insert: Insertion logic components
- injects.utils: Helper utilities for common tasks
Project Status
injects is currently in early development (v0.1.0). The core functionality is implemented, with more features planned in future releases.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file injects-0.1.0.tar.gz.
File metadata
- Download URL: injects-0.1.0.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34294dac31736cf49d2aff91dfac741732cb0f869cbc2ff221a72eb9de095741
|
|
| MD5 |
35663398a43f13085b1c2e7874a2f42b
|
|
| BLAKE2b-256 |
b58d500668d92293431cb860017b69a3c8717bdd1bed4b06e7306cc8f8ce3728
|
File details
Details for the file injects-0.1.0-py3-none-any.whl.
File metadata
- Download URL: injects-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05d18fe54fbc14e08cac19cf52952d53e8ec177eefed1bcef90c3568a4296c28
|
|
| MD5 |
d55b3b115418227504f0867398af31df
|
|
| BLAKE2b-256 |
b9e98c5b8ea46a919c71b7cfe1c501cca7868de15ce6289d83a021f0e711eb77
|