"""Utilities for JSON Schema."""from__future__importannotationsfromcopyimportdeepcopyfromtypingimportTYPE_CHECKING,Any,OptionalifTYPE_CHECKING:fromcollections.abcimportSequencedef_retrieve_ref(path:str,schema:dict)->dict:components=path.split("/")ifcomponents[0]!="#":msg=("ref paths are expected to be URI fragments, meaning they should start ""with #.")raiseValueError(msg)out=schemaforcomponentincomponents[1:]:ifcomponentinout:out=out[component]elifcomponent.isdigit():index=int(component)if(isinstance(out,list)and0<=index<len(out))or(isinstance(out,dict)andindexinout):out=out[index]else:msg=f"Reference '{path}' not found."raiseKeyError(msg)else:msg=f"Reference '{path}' not found."raiseKeyError(msg)returndeepcopy(out)def_dereference_refs_helper(obj:Any,full_schema:dict[str,Any],processed_refs:Optional[set[str]],skip_keys:Sequence[str],shallow_refs:bool,# noqa: FBT001)->Any:"""Inline every pure {'$ref':...}. But: - if shallow_refs=True: only break cycles, do not inline nested refs - if shallow_refs=False: deep-inline all nested refs Also skip recursion under any key in skip_keys. """ifprocessed_refsisNone:processed_refs=set()# 1) Pure $ref node?ifisinstance(obj,dict)andset(obj.keys())=={"$ref"}:ref_path=obj["$ref"]# cycle?ifref_pathinprocessed_refs:return{}processed_refs.add(ref_path)# grab + copy the targettarget=deepcopy(_retrieve_ref(ref_path,full_schema))# deep inlining: recurse into everythingresult=_dereference_refs_helper(target,full_schema,processed_refs,skip_keys,shallow_refs)processed_refs.remove(ref_path)returnresult# 2) Not a pure-$ref: recurse, skipping any keys in skip_keysifisinstance(obj,dict):out:dict[str,Any]={}fork,vinobj.items():ifkinskip_keys:# do not recurse under this keyout[k]=deepcopy(v)elifisinstance(v,(dict,list)):out[k]=_dereference_refs_helper(v,full_schema,processed_refs,skip_keys,shallow_refs)else:out[k]=vreturnoutifisinstance(obj,list):return[_dereference_refs_helper(item,full_schema,processed_refs,skip_keys,shallow_refs)foriteminobj]returnobj
[docs]defdereference_refs(schema_obj:dict,*,full_schema:Optional[dict]=None,skip_keys:Optional[Sequence[str]]=None,)->dict:"""Try to substitute $refs in JSON Schema. Args: schema_obj: The fragment to dereference. full_schema: The complete schema (defaults to schema_obj). skip_keys: - If None (the default), we skip recursion under '$defs' *and* only shallow-inline refs. - If provided (even as an empty list), we will recurse under every key and deep-inline all refs. """full=full_schemaorschema_objkeys_to_skip=list(skip_keys)ifskip_keysisnotNoneelse["$defs"]shallow=skip_keysisNonereturn_dereference_refs_helper(schema_obj,full,None,keys_to_skip,shallow)