make openapi_schema_pydantic opt (#9408)

pull/9417/head
Bagatur 11 months ago committed by GitHub
parent 8f2d321dd0
commit 8c986221e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,10 +1,11 @@
from __future__ import annotations
import json import json
import re import re
from collections import defaultdict from collections import defaultdict
from typing import Any, Callable, Dict, List, Optional, Tuple, Union from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
import requests import requests
from openapi_schema_pydantic import Parameter
from requests import Response from requests import Response
from langchain import LLMChain from langchain import LLMChain
@ -20,6 +21,9 @@ from langchain.tools import APIOperation
from langchain.utilities.openapi import OpenAPISpec from langchain.utilities.openapi import OpenAPISpec
from langchain.utils.input import get_colored_text from langchain.utils.input import get_colored_text
if TYPE_CHECKING:
from openapi_schema_pydantic import Parameter
def _get_description(o: Any, prefer_short: bool) -> Optional[str]: def _get_description(o: Any, prefer_short: bool) -> Optional[str]:
summary = getattr(o, "summary", None) summary = getattr(o, "summary", None)

@ -1,7 +1,19 @@
"""Pydantic models for parsing an OpenAPI spec.""" """Pydantic models for parsing an OpenAPI spec."""
from __future__ import annotations
import logging import logging
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Optional,
Sequence,
Tuple,
Type,
Union,
)
from langchain.pydantic_v1 import _PYDANTIC_MAJOR_VERSION, BaseModel, Field from langchain.pydantic_v1 import _PYDANTIC_MAJOR_VERSION, BaseModel, Field
from langchain.tools.openapi.utils.openapi_utils import HTTPVerb, OpenAPISpec from langchain.tools.openapi.utils.openapi_utils import HTTPVerb, OpenAPISpec
@ -84,13 +96,13 @@ class APIPropertyBase(BaseModel):
if _PYDANTIC_MAJOR_VERSION == 1: if _PYDANTIC_MAJOR_VERSION == 1:
from openapi_schema_pydantic import ( if TYPE_CHECKING:
MediaType, from openapi_schema_pydantic import (
Parameter, MediaType,
Reference, Parameter,
RequestBody, RequestBody,
Schema, Schema,
) )
class APIProperty(APIPropertyBase): class APIProperty(APIPropertyBase):
"""A model for a property in the query, path, header, or cookie params.""" """A model for a property in the query, path, header, or cookie params."""
@ -118,6 +130,11 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def _get_schema_type_for_array( def _get_schema_type_for_array(
schema: Schema, schema: Schema,
) -> Optional[Union[str, Tuple[str, ...]]]: ) -> Optional[Union[str, Tuple[str, ...]]]:
from openapi_schema_pydantic import (
Reference,
Schema,
)
items = schema.items items = schema.items
if isinstance(items, Schema): if isinstance(items, Schema):
schema_type = APIProperty._cast_schema_list_type(items) schema_type = APIProperty._cast_schema_list_type(items)
@ -175,6 +192,11 @@ if _PYDANTIC_MAJOR_VERSION == 1:
@staticmethod @staticmethod
def _get_schema(parameter: Parameter, spec: OpenAPISpec) -> Optional[Schema]: def _get_schema(parameter: Parameter, spec: OpenAPISpec) -> Optional[Schema]:
from openapi_schema_pydantic import (
Reference,
Schema,
)
schema = parameter.param_schema schema = parameter.param_schema
if isinstance(schema, Reference): if isinstance(schema, Reference):
schema = spec.get_referenced_schema(schema) schema = spec.get_referenced_schema(schema)
@ -231,6 +253,10 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def _process_object_schema( def _process_object_schema(
cls, schema: Schema, spec: OpenAPISpec, references_used: List[str] cls, schema: Schema, spec: OpenAPISpec, references_used: List[str]
) -> Tuple[Union[str, List[str], None], List["APIRequestBodyProperty"]]: ) -> Tuple[Union[str, List[str], None], List["APIRequestBodyProperty"]]:
from openapi_schema_pydantic import (
Reference,
)
properties = [] properties = []
required_props = schema.required or [] required_props = schema.required or []
if schema.properties is None: if schema.properties is None:
@ -265,6 +291,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
spec: OpenAPISpec, spec: OpenAPISpec,
references_used: List[str], references_used: List[str],
) -> str: ) -> str:
from openapi_schema_pydantic import Reference, Schema
items = schema.items items = schema.items
if items is not None: if items is not None:
if isinstance(items, Reference): if isinstance(items, Reference):
@ -352,6 +380,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
spec: OpenAPISpec, spec: OpenAPISpec,
) -> List[APIRequestBodyProperty]: ) -> List[APIRequestBodyProperty]:
"""Process the media type of the request body.""" """Process the media type of the request body."""
from openapi_schema_pydantic import Reference
references_used = [] references_used = []
schema = media_type_obj.media_type_schema schema = media_type_obj.media_type_schema
if isinstance(schema, Reference): if isinstance(schema, Reference):

@ -7,7 +7,7 @@ import logging
import re import re
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Union from typing import TYPE_CHECKING, Dict, List, Optional, Union
import requests import requests
import yaml import yaml
@ -39,17 +39,22 @@ class HTTPVerb(str, Enum):
if _PYDANTIC_MAJOR_VERSION == 1: if _PYDANTIC_MAJOR_VERSION == 1:
from openapi_schema_pydantic import ( if TYPE_CHECKING:
Components, from openapi_schema_pydantic import (
OpenAPI, Components,
Operation, Operation,
Parameter, Parameter,
PathItem, PathItem,
Paths, Paths,
Reference, Reference,
RequestBody, RequestBody,
Schema, Schema,
) )
try:
from openapi_schema_pydantic import OpenAPI
except ImportError:
OpenAPI = object
class OpenAPISpec(OpenAPI): class OpenAPISpec(OpenAPI):
"""OpenAPI Model that removes mis-formatted parts of the spec.""" """OpenAPI Model that removes mis-formatted parts of the spec."""
@ -109,6 +114,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def _get_root_referenced_parameter(self, ref: Reference) -> Parameter: def _get_root_referenced_parameter(self, ref: Reference) -> Parameter:
"""Get the root reference or err.""" """Get the root reference or err."""
from openapi_schema_pydantic import Reference
parameter = self._get_referenced_parameter(ref) parameter = self._get_referenced_parameter(ref)
while isinstance(parameter, Reference): while isinstance(parameter, Reference):
parameter = self._get_referenced_parameter(parameter) parameter = self._get_referenced_parameter(parameter)
@ -123,12 +130,16 @@ if _PYDANTIC_MAJOR_VERSION == 1:
return schemas[ref_name] return schemas[ref_name]
def get_schema(self, schema: Union[Reference, Schema]) -> Schema: def get_schema(self, schema: Union[Reference, Schema]) -> Schema:
from openapi_schema_pydantic import Reference
if isinstance(schema, Reference): if isinstance(schema, Reference):
return self.get_referenced_schema(schema) return self.get_referenced_schema(schema)
return schema return schema
def _get_root_referenced_schema(self, ref: Reference) -> Schema: def _get_root_referenced_schema(self, ref: Reference) -> Schema:
"""Get the root reference or err.""" """Get the root reference or err."""
from openapi_schema_pydantic import Reference
schema = self.get_referenced_schema(ref) schema = self.get_referenced_schema(ref)
while isinstance(schema, Reference): while isinstance(schema, Reference):
schema = self.get_referenced_schema(schema) schema = self.get_referenced_schema(schema)
@ -148,6 +159,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
self, ref: Reference self, ref: Reference
) -> Optional[RequestBody]: ) -> Optional[RequestBody]:
"""Get the root request Body or err.""" """Get the root request Body or err."""
from openapi_schema_pydantic import Reference
request_body = self._get_referenced_request_body(ref) request_body = self._get_referenced_request_body(ref)
while isinstance(request_body, Reference): while isinstance(request_body, Reference):
request_body = self._get_referenced_request_body(request_body) request_body = self._get_referenced_request_body(request_body)
@ -235,6 +248,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def get_methods_for_path(self, path: str) -> List[str]: def get_methods_for_path(self, path: str) -> List[str]:
"""Return a list of valid methods for the specified path.""" """Return a list of valid methods for the specified path."""
from openapi_schema_pydantic import Operation
path_item = self._get_path_strict(path) path_item = self._get_path_strict(path)
results = [] results = []
for method in HTTPVerb: for method in HTTPVerb:
@ -244,6 +259,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
return results return results
def get_parameters_for_path(self, path: str) -> List[Parameter]: def get_parameters_for_path(self, path: str) -> List[Parameter]:
from openapi_schema_pydantic import Reference
path_item = self._get_path_strict(path) path_item = self._get_path_strict(path)
parameters = [] parameters = []
if not path_item.parameters: if not path_item.parameters:
@ -256,6 +273,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def get_operation(self, path: str, method: str) -> Operation: def get_operation(self, path: str, method: str) -> Operation:
"""Get the operation object for a given path and HTTP method.""" """Get the operation object for a given path and HTTP method."""
from openapi_schema_pydantic import Operation
path_item = self._get_path_strict(path) path_item = self._get_path_strict(path)
operation_obj = getattr(path_item, method, None) operation_obj = getattr(path_item, method, None)
if not isinstance(operation_obj, Operation): if not isinstance(operation_obj, Operation):
@ -264,6 +283,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
def get_parameters_for_operation(self, operation: Operation) -> List[Parameter]: def get_parameters_for_operation(self, operation: Operation) -> List[Parameter]:
"""Get the components for a given operation.""" """Get the components for a given operation."""
from openapi_schema_pydantic import Reference
parameters = [] parameters = []
if operation.parameters: if operation.parameters:
for parameter in operation.parameters: for parameter in operation.parameters:
@ -276,6 +297,8 @@ if _PYDANTIC_MAJOR_VERSION == 1:
self, operation: Operation self, operation: Operation
) -> Optional[RequestBody]: ) -> Optional[RequestBody]:
"""Get the request body for a given operation.""" """Get the request body for a given operation."""
from openapi_schema_pydantic import Reference
request_body = operation.requestBody request_body = operation.requestBody
if isinstance(request_body, Reference): if isinstance(request_body, Reference):
request_body = self._get_root_referenced_request_body(request_body) request_body = self._get_root_referenced_request_body(request_body)

@ -5637,7 +5637,7 @@ name = "openapi-schema-pydantic"
version = "1.2.4" version = "1.2.4"
description = "OpenAPI (v3) specification schema as pydantic class" description = "OpenAPI (v3) specification schema as pydantic class"
category = "main" category = "main"
optional = false optional = true
python-versions = ">=3.6.1" python-versions = ">=3.6.1"
files = [ files = [
{file = "openapi-schema-pydantic-1.2.4.tar.gz", hash = "sha256:3e22cf58b74a69f752cc7e5f1537f6e44164282db2700cbbcd3bb99ddd065196"}, {file = "openapi-schema-pydantic-1.2.4.tar.gz", hash = "sha256:3e22cf58b74a69f752cc7e5f1537f6e44164282db2700cbbcd3bb99ddd065196"},
@ -10477,7 +10477,7 @@ clarifai = ["clarifai"]
cohere = ["cohere"] cohere = ["cohere"]
docarray = ["docarray"] docarray = ["docarray"]
embeddings = ["sentence-transformers"] embeddings = ["sentence-transformers"]
extended-testing = ["amazon-textract-caller", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "esprima", "faiss-cpu", "feedparser", "geopandas", "gitpython", "gql", "html2text", "jinja2", "jq", "lxml", "mwparserfromhell", "mwxml", "newspaper3k", "openai", "openai", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "requests-toolbelt", "scikit-learn", "streamlit", "sympy", "telethon", "tqdm", "xata", "xmltodict"] extended-testing = ["amazon-textract-caller", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "esprima", "faiss-cpu", "feedparser", "geopandas", "gitpython", "gql", "html2text", "jinja2", "jq", "lxml", "mwparserfromhell", "mwxml", "newspaper3k", "openai", "openai", "openapi-schema-pydantic", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "requests-toolbelt", "scikit-learn", "streamlit", "sympy", "telethon", "tqdm", "xata", "xmltodict"]
javascript = ["esprima"] javascript = ["esprima"]
llms = ["clarifai", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openlm", "torch", "transformers"] llms = ["clarifai", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openlm", "torch", "transformers"]
openai = ["openai", "tiktoken"] openai = ["openai", "tiktoken"]
@ -10487,4 +10487,4 @@ text-helpers = ["chardet"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
content-hash = "594d1f6ea7a3e00f0ab6c74cab8b75245d112a84635af440df7ab1242d464140" content-hash = "a5e3458dd0cabcefd83caec6eb33b6fb593c2c347ca1d33c1f182341e852a9c8"

@ -19,7 +19,7 @@ PyYAML = ">=5.3"
numpy = "^1" numpy = "^1"
azure-core = {version = "^1.26.4", optional=true} azure-core = {version = "^1.26.4", optional=true}
tqdm = {version = ">=4.48.0", optional = true} tqdm = {version = ">=4.48.0", optional = true}
openapi-schema-pydantic = "^1.2" openapi-schema-pydantic = {version = "^1.2", optional = true}
faiss-cpu = {version = "^1", optional = true} faiss-cpu = {version = "^1", optional = true}
wikipedia = {version = "^1", optional = true} wikipedia = {version = "^1", optional = true}
elasticsearch = {version = "^8", optional = true} elasticsearch = {version = "^8", optional = true}
@ -336,6 +336,7 @@ extended_testing = [
"xata", "xata",
"xmltodict", "xmltodict",
"faiss-cpu", "faiss-cpu",
"openapi-schema-pydantic",
] ]
[tool.ruff] [tool.ruff]

@ -41,7 +41,6 @@ def test_required_dependencies(poetry_conf: Mapping[str, Any]) -> None:
"langsmith", "langsmith",
"numexpr", "numexpr",
"numpy", "numpy",
"openapi-schema-pydantic",
"pydantic", "pydantic",
"python", "python",
"requests", "requests",

@ -18,14 +18,6 @@ if _PYDANTIC_MAJOR_VERSION != 1:
import pytest import pytest
import yaml import yaml
from openapi_schema_pydantic import (
Components,
Info,
MediaType,
Reference,
RequestBody,
Schema,
)
from langchain.tools.openapi.utils.api_models import ( from langchain.tools.openapi.utils.api_models import (
APIOperation, APIOperation,
@ -86,30 +78,38 @@ def http_paths_and_methods() -> List[Tuple[str, OpenAPISpec, str, str]]:
return http_paths_and_methods return http_paths_and_methods
@pytest.mark.parametrize( @pytest.mark.requires("openapi_schema_pydantic")
"spec_name, spec, path, method", def test_parse_api_operations() -> None:
http_paths_and_methods(),
)
def test_parse_api_operations(
spec_name: str, spec: OpenAPISpec, path: str, method: str
) -> None:
"""Test the APIOperation class.""" """Test the APIOperation class."""
try: for spec_name, spec, path, method in http_paths_and_methods():
APIOperation.from_openapi_spec(spec, path, method) try:
except Exception as e: APIOperation.from_openapi_spec(spec, path, method)
raise AssertionError(f"Error processing {spec_name}: {e} ") from e except Exception as e:
raise AssertionError(f"Error processing {spec_name}: {e} ") from e
@pytest.mark.requires("openapi_schema_pydantic")
@pytest.fixture @pytest.fixture
def raw_spec() -> OpenAPISpec: def raw_spec() -> OpenAPISpec:
"""Return a raw OpenAPI spec.""" """Return a raw OpenAPI spec."""
from openapi_schema_pydantic import Info
return OpenAPISpec( return OpenAPISpec(
info=Info(title="Test API", version="1.0.0"), info=Info(title="Test API", version="1.0.0"),
) )
@pytest.mark.requires("openapi_schema_pydantic")
def test_api_request_body_from_request_body_with_ref(raw_spec: OpenAPISpec) -> None: def test_api_request_body_from_request_body_with_ref(raw_spec: OpenAPISpec) -> None:
"""Test instantiating APIRequestBody from RequestBody with a reference.""" """Test instantiating APIRequestBody from RequestBody with a reference."""
from openapi_schema_pydantic import (
Components,
MediaType,
Reference,
RequestBody,
Schema,
)
raw_spec.components = Components( raw_spec.components = Components(
schemas={ schemas={
"Foo": Schema( "Foo": Schema(
@ -140,8 +140,15 @@ def test_api_request_body_from_request_body_with_ref(raw_spec: OpenAPISpec) -> N
assert api_request_body.media_type == "application/json" assert api_request_body.media_type == "application/json"
@pytest.mark.requires("openapi_schema_pydantic")
def test_api_request_body_from_request_body_with_schema(raw_spec: OpenAPISpec) -> None: def test_api_request_body_from_request_body_with_schema(raw_spec: OpenAPISpec) -> None:
"""Test instantiating APIRequestBody from RequestBody with a schema.""" """Test instantiating APIRequestBody from RequestBody with a schema."""
from openapi_schema_pydantic import (
MediaType,
RequestBody,
Schema,
)
request_body = RequestBody( request_body = RequestBody(
content={ content={
"application/json": MediaType( "application/json": MediaType(
@ -164,7 +171,14 @@ def test_api_request_body_from_request_body_with_schema(raw_spec: OpenAPISpec) -
assert api_request_body.media_type == "application/json" assert api_request_body.media_type == "application/json"
@pytest.mark.requires("openapi_schema_pydantic")
def test_api_request_body_property_from_schema(raw_spec: OpenAPISpec) -> None: def test_api_request_body_property_from_schema(raw_spec: OpenAPISpec) -> None:
from openapi_schema_pydantic import (
Components,
Reference,
Schema,
)
raw_spec.components = Components( raw_spec.components = Components(
schemas={ schemas={
"Bar": Schema( "Bar": Schema(

Loading…
Cancel
Save