Skip to main content

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.

Metadata

Release files for injects 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for injects 0.1.0
File Size Uploaded
injects-0.1.0.tar.gz 17.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for injects 0.1.0
File Interpreter ABI Platform
injects-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 37.4 kB

Release files / injects-0.1.0.tar.gz

Download URL injects-0.1.0.tar.gz
Size 17.1 kB
Tags Source
SHA-256 checksum
How to use checksums
34294dac31736cf49d2aff91dfac741732cb0f869cbc2ff221a72eb9de095741
BLAKE2b-256 checksum
How to use checksums
b58d500668d92293431cb860017b69a3c8717bdd1bed4b06e7306cc8f8ce3728
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.2

Release files / injects-0.1.0-py3-none-any.whl

Download URL injects-0.1.0-py3-none-any.whl
Size 20.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05d18fe54fbc14e08cac19cf52952d53e8ec177eefed1bcef90c3568a4296c28
BLAKE2b-256 checksum
How to use checksums
b9e98c5b8ea46a919c71b7cfe1c501cca7868de15ce6289d83a021f0e711eb77
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.2

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page