Extract Python API signatures and detect breaking changes for documentation generation.
—
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.
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)
"""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)
"""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)
"""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)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")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)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)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)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)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}")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 ObjectInstall with Tessl CLI
npx tessl i tessl/pypi-griffe