CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-griffe

Extract Python API signatures and detect breaking changes for documentation generation.

Pending
Overview
Eval results
Files

serialization.mddocs/

Serialization

JSON encoding and decoding capabilities for Griffe objects. This system enables serializing complete API structures to JSON format for storage, transmission, or external processing, and deserializing them back to full Griffe objects.

Capabilities

JSON Encoding

Custom JSON encoder for serializing Griffe objects to JSON format.

import json

class JSONEncoder(json.JSONEncoder):
    """
    JSON encoder for Griffe objects.
    
    Enables serialization of Griffe data structures to JSON format.
    Handles all Griffe object types including modules, classes, functions,
    attributes, aliases, and their associated metadata.
    """
    
    def default(self, obj: Any) -> Any:
        """
        Convert Griffe objects to JSON-serializable formats.
        
        Args:
            obj: Object to serialize (Griffe object or standard type)
            
        Returns:
            Any: JSON-serializable representation
            
        Raises:
            TypeError: If object cannot be serialized
            
        Examples:
            Basic usage:
            >>> encoder = griffe.JSONEncoder()
            >>> json_str = json.dumps(module, cls=griffe.JSONEncoder)
            
            With custom options:
            >>> encoder = griffe.JSONEncoder(indent=2, sort_keys=True)
            >>> json_str = encoder.encode(module)
        """

JSON Decoding

Function for deserializing JSON data back to Griffe objects.

def json_decoder(dct: dict[str, Any]) -> dict[str, Any] | Object:
    """
    Decode Griffe objects from JSON.
    
    JSON decoder that reconstructs Griffe objects from their serialized
    representations. Works with JSON data produced by JSONEncoder.
    
    Args:
        dct: Dictionary from JSON decoder containing object data
        
    Returns:
        dict[str, Any] | Object: Decoded Griffe object or plain dictionary
        
    Examples:
        Decode from JSON string:
        >>> json_data = '{"kind": "module", "name": "example", ...}'
        >>> obj = json.loads(json_data, object_hook=griffe.json_decoder)
        
        Decode from file:
        >>> with open("api.json") as f:
        ...     data = json.load(f, object_hook=griffe.json_decoder)
    """

Object Serialization Methods

Built-in serialization methods available on all Griffe objects.

# Available on all Object subclasses (Module, Class, Function, etc.)

def serialize(self, **kwargs: Any) -> dict[str, Any]:
    """
    Serialize the object to a dictionary.
    
    Converts the Griffe object to a dictionary representation
    suitable for JSON encoding or other serialization formats.
    
    Args:
        **kwargs: Serialization options including:
            - full: Include all details (default: False)
            - docstring_parser: Parser for docstrings
            - docstring_options: Options for docstring parsing
            
    Returns:
        dict[str, Any]: Dictionary representation of the object
        
    Examples:
        Basic serialization:
        >>> data = module.serialize()
        
        Full serialization with all details:
        >>> data = module.serialize(full=True)
        
        With docstring parsing:
        >>> data = module.serialize(
        ...     docstring_parser="google",
        ...     docstring_options={"style": "google"}
        ... )
    """

def as_json(self, **kwargs: Any) -> str:
    """
    Serialize the object to JSON string.
    
    Convenience method that combines serialize() and JSON encoding
    in a single call.
    
    Args:
        **kwargs: Arguments passed to serialize() and json.dumps()
        
    Returns:
        str: JSON string representation
        
    Examples:
        Basic JSON export:
        >>> json_str = module.as_json()
        
        Pretty-printed JSON:
        >>> json_str = module.as_json(indent=2)
        
        Full serialization to JSON:
        >>> json_str = module.as_json(full=True, indent=2)
    """

@classmethod
def from_json(cls, json_str: str, **kwargs: Any) -> Object:
    """
    Deserialize an object from JSON string.
    
    Class method that reconstructs a Griffe object from its
    JSON representation.
    
    Args:
        json_str: JSON string to deserialize
        **kwargs: Deserialization options
        
    Returns:
        Object: Reconstructed Griffe object
        
    Examples:
        Deserialize module:
        >>> json_data = module.as_json()
        >>> restored = griffe.Module.from_json(json_data)
        
        Deserialize any object type:
        >>> restored = griffe.Object.from_json(json_data)
    """

Usage Examples

Basic Serialization

import griffe
import json

# Load a package  
package = griffe.load("requests")

# Serialize to dictionary
data = package.serialize()
print(f"Package data keys: {list(data.keys())}")

# Serialize to JSON string
json_str = package.as_json(indent=2)
print("JSON representation:")
print(json_str[:200] + "...")

# Use custom JSON encoder
encoder = griffe.JSONEncoder(indent=2, sort_keys=True)
custom_json = encoder.encode(package)

Full API Serialization

import griffe

# Load package with full details
package = griffe.load("mypackage")

# Serialize with complete information
full_data = package.serialize(
    full=True,
    docstring_parser="google",
    docstring_options={"style": "google"}
)

# Export to JSON file
with open("api_documentation.json", "w") as f:
    json.dump(full_data, f, cls=griffe.JSONEncoder, indent=2)

print(f"Exported API documentation to api_documentation.json")

Deserialization

import griffe
import json

# Load from JSON file
with open("api_documentation.json") as f:
    restored_package = json.load(f, object_hook=griffe.json_decoder)

print(f"Restored package: {restored_package.name}")
print(f"Modules: {list(restored_package.modules.keys())}")

# Alternative: use from_json class method
json_str = open("api_documentation.json").read()
restored = griffe.Module.from_json(json_str)

Roundtrip Serialization

import griffe

# Original package
original = griffe.load("requests")

# Serialize and deserialize
json_data = original.as_json(full=True)
restored = griffe.Module.from_json(json_data)

# Compare
print(f"Original: {original.name}")
print(f"Restored: {restored.name}")
print(f"Same classes: {set(original.classes.keys()) == set(restored.classes.keys())}")

# Detailed comparison
def compare_objects(obj1, obj2, path=""):
    """Compare two Griffe objects recursively."""
    if obj1.name != obj2.name:
        print(f"Name mismatch at {path}: {obj1.name} != {obj2.name}")
    
    if type(obj1) != type(obj2):
        print(f"Type mismatch at {path}: {type(obj1)} != {type(obj2)}")
    
    # Compare specific attributes based on object type
    if hasattr(obj1, 'functions') and hasattr(obj2, 'functions'):
        if set(obj1.functions.keys()) != set(obj2.functions.keys()):
            print(f"Function mismatch at {path}")

compare_objects(original, restored)

CLI Integration

import griffe
import sys

def export_api_json(package_name: str, output_file: str, full: bool = False):
    """Export package API to JSON file."""
    try:
        # Load package
        package = griffe.load(package_name)
        
        # Serialize
        if full:
            data = package.serialize(full=True, docstring_parser="auto")
        else:
            data = package.serialize()
        
        # Export
        with open(output_file, "w") as f:
            json.dump(data, f, cls=griffe.JSONEncoder, indent=2)
        
        print(f"✅ Exported {package_name} API to {output_file}")
        return 0
        
    except Exception as e:
        print(f"❌ Error exporting API: {e}")
        return 1

# Command-line usage
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python script.py <package_name> <output_file> [--full]")
        sys.exit(1)
    
    package_name = sys.argv[1]
    output_file = sys.argv[2]
    full = "--full" in sys.argv
    
    exit_code = export_api_json(package_name, output_file, full)
    sys.exit(exit_code)

Custom Serialization

import griffe
import json
from typing import Any

class CustomAPIExporter:
    """Custom API exporter with filtering and transformation."""
    
    def __init__(
        self, 
        include_private: bool = False,
        include_docstrings: bool = True,
        transform_names: bool = False
    ):
        self.include_private = include_private
        self.include_docstrings = include_docstrings
        self.transform_names = transform_names
    
    def export_module(self, module: griffe.Module) -> dict[str, Any]:
        """Export module with custom filtering."""
        data = {
            "name": module.name,
            "type": "module",
            "path": module.path,
        }
        
        if self.include_docstrings and module.docstring:
            data["docstring"] = module.docstring.value
        
        # Filter and export functions
        functions = {}
        for name, func in module.functions.items():
            if not self.include_private and name.startswith("_"):
                continue
            functions[name] = self.export_function(func)
        data["functions"] = functions
        
        # Filter and export classes
        classes = {}
        for name, cls in module.classes.items():
            if not self.include_private and name.startswith("_"):
                continue
            classes[name] = self.export_class(cls)
        data["classes"] = classes
        
        return data
    
    def export_function(self, func: griffe.Function) -> dict[str, Any]:
        """Export function with custom format."""
        data = {
            "name": func.name,
            "type": "function",
            "signature": func.signature,
        }
        
        if self.include_docstrings and func.docstring:
            data["docstring"] = func.docstring.value
        
        # Transform parameter names if requested
        params = []
        for param in func.parameters:
            param_data = {"name": param.name, "kind": param.kind.name}
            if param.annotation:
                param_data["type"] = str(param.annotation)
            if param.default:
                param_data["default"] = str(param.default)
            params.append(param_data)
        
        data["parameters"] = params
        return data
    
    def export_class(self, cls: griffe.Class) -> dict[str, Any]:
        """Export class with custom format."""
        data = {
            "name": cls.name,
            "type": "class",
            "bases": [str(base) for base in cls.bases],
        }
        
        if self.include_docstrings and cls.docstring:
            data["docstring"] = cls.docstring.value
        
        # Export methods
        methods = {}
        for name, method in cls.methods.items():
            if not self.include_private and name.startswith("_"):
                continue
            methods[name] = self.export_function(method)
        data["methods"] = methods
        
        return data

# Use custom exporter
exporter = CustomAPIExporter(
    include_private=False,
    include_docstrings=True,
    transform_names=True
)

package = griffe.load("requests")
custom_data = exporter.export_module(package)

with open("custom_api.json", "w") as f:
    json.dump(custom_data, f, indent=2)

Incremental Serialization

import griffe
import json
import hashlib
from pathlib import Path

class IncrementalAPISerializer:
    """Serialize only changed parts of API."""
    
    def __init__(self, cache_dir: str = ".griffe_cache"):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
    
    def get_object_hash(self, obj: griffe.Object) -> str:
        """Get hash of object for change detection."""
        data = obj.serialize()
        json_str = json.dumps(data, sort_keys=True, cls=griffe.JSONEncoder)
        return hashlib.sha256(json_str.encode()).hexdigest()
    
    def serialize_if_changed(self, obj: griffe.Object, force: bool = False) -> bool:
        """Serialize object only if it has changed."""
        obj_hash = self.get_object_hash(obj)
        cache_file = self.cache_dir / f"{obj.path}.json"
        hash_file = self.cache_dir / f"{obj.path}.hash"
        
        # Check if already cached and unchanged
        if not force and hash_file.exists():
            with open(hash_file) as f:
                cached_hash = f.read().strip()
            if cached_hash == obj_hash:
                return False  # No changes
        
        # Serialize the object
        data = obj.serialize(full=True)
        with open(cache_file, "w") as f:
            json.dump(data, f, cls=griffe.JSONEncoder, indent=2)
        
        # Update hash
        with open(hash_file, "w") as f:
            f.write(obj_hash)
        
        return True  # Changes detected and serialized

# Use incremental serializer
serializer = IncrementalAPISerializer()

package = griffe.load("mypackage")
for module_name, module in package.modules.items():
    if serializer.serialize_if_changed(module):
        print(f"Serialized changed module: {module_name}")
    else:
        print(f"No changes in module: {module_name}")

Types

import json
from typing import Any, dict

# JSON encoder/decoder types
JSONEncoder = json.JSONEncoder

# Serialization function signatures
def serialize(**kwargs: Any) -> dict[str, Any]: ...
def as_json(**kwargs: Any) -> str: ...
def from_json(json_str: str, **kwargs: Any) -> Object: ...
def json_decoder(dct: dict[str, Any]) -> dict[str, Any] | Object: ...

# Core object type
from griffe import Object

Install with Tessl CLI

npx tessl i tessl/pypi-griffe

docs

agents.md

breaking-changes.md

cli.md

docstrings.md

extensions.md

index.md

loaders.md

models.md

serialization.md

tile.json