Source code for langchain_community.utilities.openapi
"""Utility functions for parsing an OpenAPI spec."""from__future__importannotationsimportcopyimportjsonimportloggingimportrefromenumimportEnumfrompathlibimportPathfromtypingimportTYPE_CHECKING,Dict,List,Optional,UnionimportrequestsimportyamlfrompydanticimportValidationErrorlogger=logging.getLogger(__name__)
[docs]classHTTPVerb(str,Enum):"""Enumerator of the HTTP verbs."""GET="get"PUT="put"POST="post"DELETE="delete"OPTIONS="options"HEAD="head"PATCH="patch"TRACE="trace"@classmethoddeffrom_str(cls,verb:str)->HTTPVerb:"""Parse an HTTP verb."""try:returncls(verb)exceptValueError:raiseValueError(f"Invalid HTTP verb. Valid values are {cls.__members__}")
[docs]classOpenAPISpec(OpenAPI):"""OpenAPI Model that removes mis-formatted parts of the spec."""openapi:str="3.1.0"# overriding overly restrictive type from parent class@propertydef_paths_strict(self)->Paths:ifnotself.paths:raiseValueError("No paths found in spec")returnself.pathsdef_get_path_strict(self,path:str)->PathItem:path_item=self._paths_strict.get(path)ifnotpath_item:raiseValueError(f"No path found for {path}")returnpath_item@propertydef_components_strict(self)->Components:"""Get components or err."""ifself.componentsisNone:raiseValueError("No components found in spec. ")returnself.components@propertydef_parameters_strict(self)->Dict[str,Union[Parameter,Reference]]:"""Get parameters or err."""parameters=self._components_strict.parametersifparametersisNone:raiseValueError("No parameters found in spec. ")returnparameters@propertydef_schemas_strict(self)->Dict[str,Schema]:"""Get the dictionary of schemas or err."""schemas=self._components_strict.schemasifschemasisNone:raiseValueError("No schemas found in spec. ")returnschemas@propertydef_request_bodies_strict(self)->Dict[str,Union[RequestBody,Reference]]:"""Get the request body or err."""request_bodies=self._components_strict.requestBodiesifrequest_bodiesisNone:raiseValueError("No request body found in spec. ")returnrequest_bodiesdef_get_referenced_parameter(self,ref:Reference)->Union[Parameter,Reference]:"""Get a parameter (or nested reference) or err."""ref_name=ref.ref.split("/")[-1]parameters=self._parameters_strictifref_namenotinparameters:raiseValueError(f"No parameter found for {ref_name}")returnparameters[ref_name]def_get_root_referenced_parameter(self,ref:Reference)->Parameter:"""Get the root reference or err."""fromopenapi_pydanticimportReferenceparameter=self._get_referenced_parameter(ref)whileisinstance(parameter,Reference):parameter=self._get_referenced_parameter(parameter)returnparameter
[docs]defget_referenced_schema(self,ref:Reference)->Schema:"""Get a schema (or nested reference) or err."""ref_name=ref.ref.split("/")[-1]schemas=self._schemas_strictifref_namenotinschemas:raiseValueError(f"No schema found for {ref_name}")returnschemas[ref_name]
[docs]defget_schema(self,schema:Union[Reference,Schema],depth:int=0,max_depth:Optional[int]=None,)->Schema:ifmax_depthisnotNoneanddepth>=max_depth:raiseRecursionError(f"Max depth of {max_depth} has been exceeded when resolving references.")fromopenapi_pydanticimportReferenceifisinstance(schema,Reference):schema=self.get_referenced_schema(schema)# TODO: Resolve references on all fields of Schema ?# (e.g. patternProperties, etc...)ifschema.propertiesisnotNone:forp_name,pinschema.properties.items():schema.properties[p_name]=self.get_schema(p,depth+1,max_depth)ifschema.itemsisnotNone:schema.items=self.get_schema(schema.items,depth+1,max_depth)returnschema
def_get_root_referenced_schema(self,ref:Reference)->Schema:"""Get the root reference or err."""fromopenapi_pydanticimportReferenceschema=self.get_referenced_schema(ref)whileisinstance(schema,Reference):schema=self.get_referenced_schema(schema)returnschemadef_get_referenced_request_body(self,ref:Reference)->Optional[Union[Reference,RequestBody]]:"""Get a request body (or nested reference) or err."""ref_name=ref.ref.split("/")[-1]request_bodies=self._request_bodies_strictifref_namenotinrequest_bodies:raiseValueError(f"No request body found for {ref_name}")returnrequest_bodies[ref_name]def_get_root_referenced_request_body(self,ref:Reference)->Optional[RequestBody]:"""Get the root request Body or err."""fromopenapi_pydanticimportReferencerequest_body=self._get_referenced_request_body(ref)whileisinstance(request_body,Reference):request_body=self._get_referenced_request_body(request_body)returnrequest_body@staticmethoddef_alert_unsupported_spec(obj:dict)->None:"""Alert if the spec is not supported."""warning_message=(" This may result in degraded performance."+" Convert your OpenAPI spec to 3.1.* spec"+" for better support.")swagger_version=obj.get("swagger")openapi_version=obj.get("openapi")ifisinstance(openapi_version,str):ifopenapi_version!="3.1.0":logger.warning(f"Attempting to load an OpenAPI {openapi_version}"f" spec. {warning_message}")else:passelifisinstance(swagger_version,str):logger.warning(f"Attempting to load a Swagger {swagger_version}"f" spec. {warning_message}")else:raiseValueError(f"Attempting to load an unsupported spec:\n\n{obj}\n{warning_message}")
[docs]@classmethoddefparse_obj(cls,obj:dict)->OpenAPISpec:try:cls._alert_unsupported_spec(obj)returnsuper().parse_obj(obj)exceptValidationErrorase:# We are handling possibly misconfigured specs and# want to do a best-effort job to get a reasonable interface out of it.new_obj=copy.deepcopy(obj)forerrorine.errors():keys=error["loc"]item=new_objforkeyinkeys[:-1]:item=item[key]item.pop(keys[-1],None)returncls.parse_obj(new_obj)
[docs]@classmethoddeffrom_spec_dict(cls,spec_dict:dict)->OpenAPISpec:"""Get an OpenAPI spec from a dict."""returncls.parse_obj(spec_dict)
[docs]@classmethoddeffrom_text(cls,text:str)->OpenAPISpec:"""Get an OpenAPI spec from a text."""try:spec_dict=json.loads(text)exceptjson.JSONDecodeError:spec_dict=yaml.safe_load(text)returncls.from_spec_dict(spec_dict)
[docs]@classmethoddeffrom_file(cls,path:Union[str,Path])->OpenAPISpec:"""Get an OpenAPI spec from a file path."""path_=pathifisinstance(path,Path)elsePath(path)ifnotpath_.exists():raiseFileNotFoundError(f"{path} does not exist")withpath_.open("r")asf:returncls.from_text(f.read())
[docs]@classmethoddeffrom_url(cls,url:str)->OpenAPISpec:"""Get an OpenAPI spec from a URL."""response=requests.get(url)returncls.from_text(response.text)
@propertydefbase_url(self)->str:"""Get the base url."""returnself.servers[0].url
[docs]defget_methods_for_path(self,path:str)->List[str]:"""Return a list of valid methods for the specified path."""fromopenapi_pydanticimportOperationpath_item=self._get_path_strict(path)results=[]formethodinHTTPVerb:operation=getattr(path_item,method.value,None)ifisinstance(operation,Operation):results.append(method.value)returnresults
[docs]defget_operation(self,path:str,method:str)->Operation:"""Get the operation object for a given path and HTTP method."""fromopenapi_pydanticimportOperationpath_item=self._get_path_strict(path)operation_obj=getattr(path_item,method,None)ifnotisinstance(operation_obj,Operation):raiseValueError(f"No {method} method found for {path}")returnoperation_obj
[docs]defget_parameters_for_operation(self,operation:Operation)->List[Parameter]:"""Get the components for a given operation."""fromopenapi_pydanticimportReferenceparameters=[]ifoperation.parameters:forparameterinoperation.parameters:ifisinstance(parameter,Reference):parameter=self._get_root_referenced_parameter(parameter)parameters.append(parameter)returnparameters
[docs]defget_request_body_for_operation(self,operation:Operation)->Optional[RequestBody]:"""Get the request body for a given operation."""fromopenapi_pydanticimportReferencerequest_body=operation.requestBodyifisinstance(request_body,Reference):request_body=self._get_root_referenced_request_body(request_body)returnrequest_body
[docs]@staticmethoddefget_cleaned_operation_id(operation:Operation,path:str,method:str)->str:"""Get a cleaned operation id from an operation id."""operation_id=operation.operationIdifoperation_idisNone:# Replace all punctuation of any kind with underscorepath=re.sub(r"[^a-zA-Z0-9]","_",path.lstrip("/"))operation_id=f"{path}_{method}"returnoperation_id.replace("-","_").replace(".","_").replace("/","_")