Working with References
OpenAPI specifications use references ($ref) to avoid duplication. Cicerone provides a comprehensive API for navigating and resolving these references.
Understanding References in OpenAPI
A reference in OpenAPI uses the $ref keyword to point to another part of the specification. References follow the JSON Reference specification (RFC 6901) and use JSON Pointer syntax.
Reference Locations
References can appear in various places in an OpenAPI specification:
- Schema objects - Most common, referencing reusable schemas
- Response objects - Referencing reusable responses
- Parameter objects - Referencing reusable parameters
- Request body objects - Referencing reusable request bodies
- And more - Anywhere the spec allows a Reference Object
Basic Reference Navigation
Resolving a Reference
The most common operation is resolving a reference to get its target object as a typed Pydantic model:
from cicerone import parse as cicerone_parse
from cicerone.spec import Schema
# Load your OpenAPI spec
spec = cicerone_parse.parse_spec_from_file('openapi.yaml')
# Resolve a reference to a schema - returns a Schema object, not a dict
user_schema = spec.resolve_reference('#/components/schemas/User')
print(f"Type: {type(user_schema)}") # <class 'cicerone.spec.schema.Schema'>
print(f"User schema type: {user_schema.type}")
print(f"User properties: {list(user_schema.properties.keys())}")
Example with a sample schema:
components:
schemas:
User:
type: object
required:
- id
- username
- email
properties:
id:
type: string
username:
type: string
email:
type: string
format: email
# Resolves to a typed Schema object
user_schema = spec.resolve_reference('#/components/schemas/User')
assert isinstance(user_schema, Schema)
# Access properties directly as attributes
print(user_schema.type) # 'object'
print(user_schema.required) # ['id', 'username', 'email']
print(user_schema.properties['email'].format) # 'email'
Finding All References
You can discover all references in your specification as a dictionary:
# Get all references as a dict mapping $ref strings to Reference objects
all_refs = spec.get_all_references()
# Access specific references by their $ref string
user_ref = all_refs.get('#/components/schemas/User')
if user_ref:
print(f"Found reference: {user_ref.ref}")
# Filter by type
local_refs = {k: v for k, v in all_refs.items() if v.is_local}
external_refs = {k: v for k, v in all_refs.items() if v.is_external}
print(f"Found {len(all_refs)} total references")
print(f"Local: {len(local_refs)}, External: {len(external_refs)}")
# List all schema references
schema_refs = {k: v for k, v in all_refs.items() if '/schemas/' in k}
for ref_str, ref_obj in schema_refs.items():
print(f" - {ref_str}")
Working with the Reference Model
The Reference class has properties for inspecting reference objects:
from cicerone.references import Reference
from cicerone.spec import Schema
# Create a reference object
ref = Reference(ref='#/components/schemas/Pet')
# Check reference type
print(f"Is local: {ref.is_local}") # True
print(f"Is external: {ref.is_external}") # False
# Get the JSON Pointer
print(f"Pointer: {ref.pointer}") # /components/schemas/Pet
# Get pointer components
print(f"Parts: {ref.pointer_parts}") # ['components', 'schemas', 'Pet']
# Resolve the reference to get the actual Schema object
pet_schema = spec.resolve_reference(ref)
assert isinstance(pet_schema, Schema)
print(f"Pet schema type: {pet_schema.type}")
OAS 3.1 Summary and Description
In OpenAPI 3.1, Reference Objects can have summary and description fields:
from cicerone.references import Reference
ref = Reference(ref='#/components/schemas/User',
summary='User schema',
description='Represents a user in the system'
)
print(f"Reference: {ref.ref}")
print(f"Summary: {ref.summary}")
print(f"Description: {ref.description}")
Advanced Reference Operations
Nested References
Cicerone can automatically follow nested references:
components:
schemas:
UserList:
type: array
items:
$ref: '#/components/schemas/User'
User:
$ref: '#/components/schemas/Person'
Person:
type: object
properties:
name:
type: string
# By default, follows nested references
person = spec.resolve_reference('#/components/schemas/User')
# Returns the Person schema (fully resolved)
# Or stop at the first level
user_ref = spec.resolve_reference('#/components/schemas/User', follow_nested=False)
Circular Reference Detection
Some schemas have circular references (e.g., tree structures):
components:
schemas:
Node:
type: object
properties:
value:
type: string
children:
type: array
items:
$ref: '#/components/schemas/Node' # Circular!
Cicerone detects and handles circular references:
from cicerone.references import ReferenceResolver
# Check if a reference is circular
resolver = ReferenceResolver(spec)
is_circular = resolver.is_circular_reference('#/components/schemas/Node')
print(f"Is circular: {is_circular}")
# When resolving with follow_nested=True, circular references are handled
# by stopping recursion - the circular reference remains as a Reference object
# pointing back to create a linked-list style structure
node = spec.resolve_reference('#/components/schemas/Node', follow_nested=True)
print("Node schema resolved successfully with circular handling")
Reference Resolution Rules
OAS 3.0 vs 3.1 Differences
The behavior of references differs slightly between OpenAPI versions:
OpenAPI 3.0:
- Reference Objects can only contain
$ref - Adjacent keywords are ignored
- References fully replace the object
OpenAPI 3.1:
- Reference Objects can have
summaryanddescription - These fields override the target's values
- In Schema Objects,
$refcan coexist with other keywords (acts likeallOf)
Cicerone handles both versions correctly, preserving the raw specification data.
API Reference
OpenAPISpec Methods
resolve_reference(ref, follow_nested=True)
Resolve a reference to its target object as a typed Pydantic model.
Parameters:
ref(str or Reference): Reference to resolvefollow_nested(bool): Whether to recursively resolve nested references
Returns: Typed Pydantic model (Schema, Response, Parameter, etc.) when the reference points to a recognized component type. Otherwise returns raw data.
Raises:
ValueError: If reference cannot be resolvedRecursionError: If circular reference detected
get_all_references()
Get all references in the specification.
Returns: Dictionary mapping $ref strings to Reference objects
Example:
all_refs = spec.get_all_references()
user_ref = all_refs.get('#/components/schemas/User')
local_refs = {k: v for k, v in all_refs.items() if v.is_local}
is_circular_reference(ref)
Check if a reference creates a circular dependency.
Parameters:
ref(str or Reference): Reference to check
Returns: bool - True if circular
Reference Class
Properties
ref(str): The reference stringsummary(str | None): Summary (OAS 3.1+)description(str | None): Description (OAS 3.1+)is_local(bool): True if local reference (#...)is_external(bool): True if external referencepointer(str): JSON Pointer part (/components/schemas/User)document(str): Document part (for external refs)pointer_parts(list[str]): Pointer split into components
Methods
from_dict(data): Create from dictionaryis_reference(data): Check if data contains $ref
Best Practices
- Check for references before accessing: Use
Reference.is_reference()to check if data contains a $ref - Handle circular references: Use
is_circular_reference()before full resolution - Cache resolved references: If resolving the same reference multiple times, cache the results
- Validate references: Verify that all references in your spec can be resolved
Limitations
Current limitations (we'll address these in future versions):
- External references (file paths, URLs) are not yet supported
operationRefin Link Objects is not yet implementedmappingin Discriminator Objects is not yet implemented- Dynamic references (
$dynamicRef) from JSON Schema 2020-12 / OAS 3.1 are not yet supported