Skip to content

Instantly share code, notes, and snippets.

@Diegiwg
Forked from kmahorker/resolve_refs.py
Created March 8, 2025 12:57
Show Gist options
  • Save Diegiwg/e973f0757365d9af5e67a69caf956e78 to your computer and use it in GitHub Desktop.
Save Diegiwg/e973f0757365d9af5e67a69caf956e78 to your computer and use it in GitHub Desktop.
import json
import yaml
from typing import Dict, Any, Union
from pathlib import Path
from datetime import datetime, date
def load_spec(file_path: str) -> Dict[str, Any]:
"""Load an OpenAPI spec from a YAML or JSON file."""
path = Path(file_path)
with open(path, 'r') as f:
if path.suffix in ['.yaml', '.yml']:
return yaml.safe_load(f)
else:
return json.load(f)
def resolve_ref(ref: str, spec: Dict[str, Any]) -> Dict[str, Any]:
"""Resolve a $ref in the OpenAPI spec"""
if not ref.startswith('#/'):
raise ValueError(f"Only local references are supported: {ref}")
parts = ref.lstrip('#/').split('/')
current = spec
for part in parts:
if part not in current:
raise ValueError(f"Reference {ref} cannot be resolved. Part '{part}' not found.")
current = current[part]
return current
def resolve_refs_in_object(obj: Union[Dict[str, Any], list], spec: Dict[str, Any]) -> Union[Dict[str, Any], list]:
"""Recursively resolve all $ref references in an object."""
if isinstance(obj, dict):
resolved = {}
for key, value in obj.items():
if key == '$ref':
# Resolve the reference and continue resolving any nested refs
resolved_ref = resolve_ref(value, spec)
return resolve_refs_in_object(resolved_ref, spec)
else:
resolved[key] = resolve_refs_in_object(value, spec)
return resolved
elif isinstance(obj, list):
return [resolve_refs_in_object(item, spec) for item in obj]
else:
return obj
def resolve_all_refs(spec: Dict[str, Any]) -> Dict[str, Any]:
"""Resolve all references in an OpenAPI specification."""
resolved_spec = {}
# Process each top-level field
for key, value in spec.items():
resolved_spec[key] = resolve_refs_in_object(value, spec)
return resolved_spec
class NoAliasDumper(yaml.SafeDumper):
"""A YAML Dumper that never uses aliases."""
def ignore_aliases(self, data):
return True
class OpenAPIJSONEncoder(json.JSONEncoder):
"""Custom JSON encoder that can handle datetime objects."""
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
def main():
import argparse
parser = argparse.ArgumentParser(description='Resolve all references in an OpenAPI specification')
parser.add_argument('input_file', help='Path to input OpenAPI spec file (YAML or JSON)')
parser.add_argument('output_file', help='Path to output the resolved spec')
parser.add_argument('--format', choices=['yaml', 'json'], default='yaml',
help='Output format (default: yaml)')
args = parser.parse_args()
# Load the spec
spec = load_spec(args.input_file)
# Resolve all references
resolved_spec = resolve_all_refs(spec)
# Write the output
with open(args.output_file, 'w') as f:
if args.format == 'yaml':
yaml.dump(resolved_spec, f, sort_keys=False, Dumper=NoAliasDumper)
else:
json.dump(resolved_spec, f, indent=2, cls=OpenAPIJSONEncoder)
print(f"Successfully resolved all references and saved to {args.output_file}")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment