CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-astroid

An abstract syntax tree for Python with inference support.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

bases.mddocs/

Base Classes and Objects

Foundation classes for instances, methods, and other inferred objects that represent runtime Python objects within the static analysis context. These classes bridge the gap between static AST nodes and runtime object behavior.

Capabilities

Base Instance Classes

Foundation classes that represent runtime objects during static analysis.

class BaseInstance:
    """
    Base class for all runtime object representations.
    
    Provides common functionality for objects that can be
    inferred during static analysis but represent runtime entities.
    """
    
    def __init__(self, instance_type: NodeNG) -> None:
        """Initialize with the type that created this instance."""
    
    def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
        """
        Infer this instance.
        
        Parameters:
        - context: Inference context
        
        Yields:
        This instance (instances infer to themselves)
        """
    
    def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
        """
        Get attribute values for this instance.
        
        Parameters:
        - name: Attribute name to look up
        - context: Inference context
        
        Returns:
        List of possible attribute values
        
        Raises:
        AttributeInferenceError: If attribute cannot be found
        """
    
    def igetattr(self, name: str, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
        """
        Iterate over possible attribute values.
        
        Parameters:
        - name: Attribute name
        - context: Inference context
        
        Yields:
        Possible attribute values
        """

class Instance(BaseInstance):
    """
    Represents an instance of a class.
    
    This is used when astroid can determine that a variable
    holds an instance of a specific class, allowing for
    method resolution and attribute access.
    """
    
    def __init__(self, klass: ClassDef) -> None:
        """Initialize with the class this is an instance of."""
    
    def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
        """
        Get attribute from the instance or its class.
        
        Follows Python's attribute lookup order:
        1. Instance attributes
        2. Class attributes
        3. Parent class attributes (via MRO)
        4. Descriptors and properties
        
        Parameters:
        - name: Attribute name
        - context: Inference context
        
        Returns:
        List of possible attribute values
        """
    
    def has_dynamic_getattr(self) -> bool:
        """Check if the class has __getattr__ or __getattribute__."""
    
    def _proxied(self) -> ClassDef:
        """Get the class this is an instance of."""

Method Representations

Classes representing bound and unbound methods in the static analysis context.

class UnboundMethod:
    """
    Represents an unbound method.
    
    An unbound method is a function defined in a class
    but not yet bound to a specific instance.
    """
    
    def __init__(self, proxy: FunctionDef) -> None:
        """Initialize with the function this represents."""
    
    def infer(self, context: InferenceContext | None = None) -> Iterator[UnboundMethod]:
        """Infer this unbound method (returns self)."""
    
    def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
        """Get attributes from the underlying function."""
    
    @property
    def name(self) -> str:
        """Get the method name."""
    
    @property
    def parent(self) -> ClassDef:
        """Get the class containing this method."""

class BoundMethod:
    """
    Represents a method bound to a specific instance.
    
    A bound method is created when accessing a method
    on an instance (e.g., obj.method).
    """
    
    def __init__(self, proxy: UnboundMethod, bound: Instance) -> None:
        """
        Initialize with unbound method and bound instance.
        
        Parameters:
        - proxy: The unbound method
        - bound: The instance this is bound to
        """
    
    bound: Instance
    """The instance this method is bound to."""
    
    proxy: UnboundMethod
    """The underlying unbound method."""
    
    def infer(self, context: InferenceContext | None = None) -> Iterator[BoundMethod]:
        """Infer this bound method (returns self)."""
    
    def infer_call_result(self, context: InferenceContext | None = None, **kwargs) -> Iterator[InferenceResult]:
        """
        Infer the result of calling this bound method.
        
        Parameters:
        - context: Inference context
        - kwargs: Additional call arguments
        
        Yields:
        Possible return values of the method call
        """
    
    @property
    def name(self) -> str:
        """Get the method name."""
    
    @property
    def qname(self) -> str:
        """Get the qualified method name."""

Generator and Iterator Objects

Classes representing generator and iterator objects.

class Generator(Instance):
    """
    Represents a generator object.
    
    Created when a function contains yield statements,
    representing the generator object returned by calling
    such a function.
    """
    
    def __init__(self, func: FunctionDef) -> None:
        """Initialize with the generator function."""
    
    def infer(self, context: InferenceContext | None = None) -> Iterator[Generator]:
        """Infer this generator (returns self)."""

class AsyncGenerator(Generator):
    """
    Represents an async generator object.
    
    Created when an async function contains yield statements.
    """

Proxy and Wrapper Classes

Base classes for wrapping and proxying other objects.

class Proxy:
    """
    Base class for proxy objects.
    
    Proxies delegate attribute access and method calls
    to an underlying object while potentially modifying
    or filtering the behavior.
    """
    
    def __init__(self, proxied: Any) -> None:
        """Initialize with the object to proxy."""
    
    def __getattr__(self, name: str) -> Any:
        """Delegate attribute access to proxied object."""
    
    def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
        """Infer the proxied object."""

Usage Examples

Working with Instances

import astroid

code = '''
class MyClass:
    class_attr = "class_value"
    
    def __init__(self):
        self.instance_attr = "instance_value"
    
    def method(self):
        return self.instance_attr

obj = MyClass()
value = obj.instance_attr
result = obj.method()
'''

module = astroid.parse(code)

# Find the assignment to obj
for assign in module.nodes_of_class(astroid.Assign):
    if assign.targets[0].name == 'obj':
        # Infer the assigned value
        for inferred in assign.value.infer():
            if isinstance(inferred, astroid.Instance):
                print(f"obj is an instance of {inferred._proxied.name}")
                
                # Get instance attributes
                try:
                    attrs = inferred.getattr('instance_attr')
                    print(f"instance_attr: {[attr.value for attr in attrs if hasattr(attr, 'value')]}")
                except astroid.AttributeInferenceError:
                    print("instance_attr not found")

Method Binding and Calls

import astroid

code = '''
class Calculator:
    def add(self, a, b):
        return a + b
    
    def multiply(self, x, y):
        return x * y

calc = Calculator()
add_method = calc.add
result = calc.add(5, 3)
'''

module = astroid.parse(code)

# Find method access
for attr in module.nodes_of_class(astroid.Attribute):
    if attr.attrname in ('add', 'multiply'):
        for inferred in attr.infer():
            if isinstance(inferred, astroid.BoundMethod):
                print(f"Found bound method: {inferred.name}")
                print(f"Bound to: {type(inferred.bound).__name__}")
                print(f"Underlying function: {inferred.proxy.name}")

# Find method calls
for call in module.nodes_of_class(astroid.Call):
    if isinstance(call.func, astroid.Attribute):
        for inferred in call.func.infer():
            if isinstance(inferred, astroid.BoundMethod):
                # Try to infer call result
                try:
                    for result in inferred.infer_call_result():
                        print(f"Method call result: {result}")
                except astroid.InferenceError:
                    print("Cannot infer method call result")

Generator Objects

import astroid

code = '''
def number_generator():
    for i in range(3):
        yield i

def async_generator():
    for i in range(3):
        yield i

gen = number_generator()
'''

module = astroid.parse(code)

# Find generator creation
for assign in module.nodes_of_class(astroid.Assign):
    if assign.targets[0].name == 'gen':
        for inferred in assign.value.infer():
            if isinstance(inferred, astroid.Generator):
                print(f"Found generator from function: {inferred._proxied.name}")

Custom Instance Behavior

import astroid

code = '''
class DynamicClass:
    def __init__(self):
        self.data = {}
    
    def __getattr__(self, name):
        return self.data.get(name, "default")
    
    def __setattr__(self, name, value):
        if name == 'data':
            super().__setattr__(name, value)
        else:
            self.data[name] = value

obj = DynamicClass()
obj.dynamic_attr = "value"
result = obj.dynamic_attr
'''

module = astroid.parse(code)

# Find the DynamicClass instance
for assign in module.nodes_of_class(astroid.Assign):
    if assign.targets[0].name == 'obj':
        for inferred in assign.value.infer():
            if isinstance(inferred, astroid.Instance):
                # Check for dynamic attribute behavior
                has_getattr = inferred.has_dynamic_getattr()
                print(f"Has dynamic getattr: {has_getattr}")
                
                # Try to access dynamic attributes
                try:
                    dynamic_attrs = inferred.getattr('dynamic_attr')
                    print(f"Dynamic attribute found: {len(dynamic_attrs)} possibilities")
                except astroid.AttributeInferenceError:
                    print("Dynamic attribute access not resolved")

Advanced Usage

Custom Instance Classes

import astroid

class CustomInstance(astroid.Instance):
    """Custom instance with special behavior."""
    
    def getattr(self, name, context=None):
        # Custom attribute resolution logic
        if name.startswith('special_'):
            # Return special attributes
            return [astroid.Const(value=f"Special: {name}")]
        
        # Delegate to parent for normal attributes
        return super().getattr(name, context)

# Register custom instance behavior
def create_custom_instance(klass):
    """Create custom instance instead of regular instance."""
    return CustomInstance(klass)

# This would require deeper integration with astroid's inference system

Proxy Implementation

import astroid

class LoggingProxy(astroid.Proxy):
    """Proxy that logs attribute access."""
    
    def __init__(self, proxied):
        super().__init__(proxied)
        self.access_log = []
    
    def getattr(self, name, context=None):
        self.access_log.append(name)
        return self._proxied.getattr(name, context)
    
    def log_report(self):
        return f"Accessed attributes: {self.access_log}"

Method Resolution

import astroid

def analyze_method_binding(module):
    """Analyze method binding in a module."""
    method_info = []
    
    for attr in module.nodes_of_class(astroid.Attribute):
        try:
            for inferred in attr.infer():
                if isinstance(inferred, astroid.BoundMethod):
                    info = {
                        'method_name': inferred.name,
                        'class_name': inferred.bound._proxied.name,
                        'is_bound': True
                    }
                    method_info.append(info)
                elif isinstance(inferred, astroid.UnboundMethod):
                    info = {
                        'method_name': inferred.name,
                        'class_name': inferred.parent.name,
                        'is_bound': False
                    }
                    method_info.append(info)
        except astroid.InferenceError:
            continue
    
    return method_info

Integration with Inference

The base classes integrate tightly with astroid's inference system:

  1. Instance Creation: When inferring class instantiation
  2. Method Binding: When accessing methods on instances
  3. Attribute Resolution: Following Python's attribute lookup rules
  4. Call Resolution: Determining method call results

These classes provide the foundation for astroid's sophisticated understanding of Python object behavior during static analysis.

Install with Tessl CLI

npx tessl i tessl/pypi-astroid

docs

bases.md

exceptions.md

index.md

inference.md

manager.md

nodes.md

parsing.md

tile.json