Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

injects-0.1.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

injects-0.1.0-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

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

Hashes for injects-0.1.0.tar.gz
Algorithm Hash digest
SHA256 34294dac31736cf49d2aff91dfac741732cb0f869cbc2ff221a72eb9de095741
MD5 35663398a43f13085b1c2e7874a2f42b
BLAKE2b-256 b58d500668d92293431cb860017b69a3c8717bdd1bed4b06e7306cc8f8ce3728

See more details on using hashes here.

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

Hashes for injects-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 05d18fe54fbc14e08cac19cf52952d53e8ec177eefed1bcef90c3568a4296c28
MD5 d55b3b115418227504f0867398af31df
BLAKE2b-256 b9e98c5b8ea46a919c71b7cfe1c501cca7868de15ce6289d83a021f0e711eb77

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page