mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-22 23:00:00 +00:00
Wrap OpenAPI features in conditionals for pydantic v2 compatibility (#9205)
Wrap OpenAPI in conditionals for pydantic v2 compatibility.
This commit is contained in:
parent
89be10f6b4
commit
4f1feaca83
@ -27,6 +27,12 @@ if "pydantic_v1" not in sys.modules:
|
||||
# and may run prior to langchain core package.
|
||||
sys.modules["pydantic_v1"] = pydantic_v1
|
||||
|
||||
try:
|
||||
_PYDANTIC_MAJOR_VERSION: int = int(metadata.version("pydantic").split(".")[0])
|
||||
except metadata.PackageNotFoundError:
|
||||
_PYDANTIC_MAJOR_VERSION = 0
|
||||
|
||||
|
||||
from langchain.agents import MRKLChain, ReActChain, SelfAskWithSearchChain
|
||||
from langchain.cache import BaseCache
|
||||
from langchain.chains import (
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,4 @@
|
||||
"""Utility functions for parsing an OpenAPI spec. Kept for backwards compat."""
|
||||
from langchain.utilities.openapi import HTTPVerb, OpenAPISpec # noqa: F401
|
||||
from langchain.utilities.openapi import HTTPVerb, OpenAPISpec
|
||||
|
||||
__all__ = ["HTTPVerb", "OpenAPISpec"]
|
||||
|
@ -2,9 +2,8 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import requests
|
||||
from pydantic_v1 import Extra, root_validator
|
||||
from pydantic_v1 import BaseModel, Extra, root_validator
|
||||
|
||||
from langchain.tools.base import BaseModel
|
||||
from langchain.utils import get_from_dict_or_env
|
||||
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Utility functions for parsing an OpenAPI spec."""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
@ -9,19 +11,10 @@ from typing import Dict, List, Optional, Union
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from openapi_schema_pydantic import (
|
||||
Components,
|
||||
OpenAPI,
|
||||
Operation,
|
||||
Parameter,
|
||||
PathItem,
|
||||
Paths,
|
||||
Reference,
|
||||
RequestBody,
|
||||
Schema,
|
||||
)
|
||||
from pydantic_v1 import ValidationError
|
||||
|
||||
from langchain import _PYDANTIC_MAJOR_VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -38,7 +31,7 @@ class HTTPVerb(str, Enum):
|
||||
TRACE = "trace"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, verb: str) -> "HTTPVerb":
|
||||
def from_str(cls, verb: str) -> HTTPVerb:
|
||||
"""Parse an HTTP verb."""
|
||||
try:
|
||||
return cls(verb)
|
||||
@ -46,240 +39,265 @@ class HTTPVerb(str, Enum):
|
||||
raise ValueError(f"Invalid HTTP verb. Valid values are {cls.__members__}")
|
||||
|
||||
|
||||
class OpenAPISpec(OpenAPI):
|
||||
"""OpenAPI Model that removes misformatted parts of the spec."""
|
||||
if _PYDANTIC_MAJOR_VERSION == 1:
|
||||
from openapi_schema_pydantic import (
|
||||
Components,
|
||||
OpenAPI,
|
||||
Operation,
|
||||
Parameter,
|
||||
PathItem,
|
||||
Paths,
|
||||
Reference,
|
||||
RequestBody,
|
||||
Schema,
|
||||
)
|
||||
|
||||
@property
|
||||
def _paths_strict(self) -> Paths:
|
||||
if not self.paths:
|
||||
raise ValueError("No paths found in spec")
|
||||
return self.paths
|
||||
class OpenAPISpec(OpenAPI):
|
||||
"""OpenAPI Model that removes mis-formatted parts of the spec."""
|
||||
|
||||
def _get_path_strict(self, path: str) -> PathItem:
|
||||
path_item = self._paths_strict.get(path)
|
||||
if not path_item:
|
||||
raise ValueError(f"No path found for {path}")
|
||||
return path_item
|
||||
@property
|
||||
def _paths_strict(self) -> Paths:
|
||||
if not self.paths:
|
||||
raise ValueError("No paths found in spec")
|
||||
return self.paths
|
||||
|
||||
@property
|
||||
def _components_strict(self) -> Components:
|
||||
"""Get components or err."""
|
||||
if self.components is None:
|
||||
raise ValueError("No components found in spec. ")
|
||||
return self.components
|
||||
def _get_path_strict(self, path: str) -> PathItem:
|
||||
path_item = self._paths_strict.get(path)
|
||||
if not path_item:
|
||||
raise ValueError(f"No path found for {path}")
|
||||
return path_item
|
||||
|
||||
@property
|
||||
def _parameters_strict(self) -> Dict[str, Union[Parameter, Reference]]:
|
||||
"""Get parameters or err."""
|
||||
parameters = self._components_strict.parameters
|
||||
if parameters is None:
|
||||
raise ValueError("No parameters found in spec. ")
|
||||
return parameters
|
||||
@property
|
||||
def _components_strict(self) -> Components:
|
||||
"""Get components or err."""
|
||||
if self.components is None:
|
||||
raise ValueError("No components found in spec. ")
|
||||
return self.components
|
||||
|
||||
@property
|
||||
def _schemas_strict(self) -> Dict[str, Schema]:
|
||||
"""Get the dictionary of schemas or err."""
|
||||
schemas = self._components_strict.schemas
|
||||
if schemas is None:
|
||||
raise ValueError("No schemas found in spec. ")
|
||||
return schemas
|
||||
@property
|
||||
def _parameters_strict(self) -> Dict[str, Union[Parameter, Reference]]:
|
||||
"""Get parameters or err."""
|
||||
parameters = self._components_strict.parameters
|
||||
if parameters is None:
|
||||
raise ValueError("No parameters found in spec. ")
|
||||
return parameters
|
||||
|
||||
@property
|
||||
def _request_bodies_strict(self) -> Dict[str, Union[RequestBody, Reference]]:
|
||||
"""Get the request body or err."""
|
||||
request_bodies = self._components_strict.requestBodies
|
||||
if request_bodies is None:
|
||||
raise ValueError("No request body found in spec. ")
|
||||
return request_bodies
|
||||
@property
|
||||
def _schemas_strict(self) -> Dict[str, Schema]:
|
||||
"""Get the dictionary of schemas or err."""
|
||||
schemas = self._components_strict.schemas
|
||||
if schemas is None:
|
||||
raise ValueError("No schemas found in spec. ")
|
||||
return schemas
|
||||
|
||||
def _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_strict
|
||||
if ref_name not in parameters:
|
||||
raise ValueError(f"No parameter found for {ref_name}")
|
||||
return parameters[ref_name]
|
||||
@property
|
||||
def _request_bodies_strict(self) -> Dict[str, Union[RequestBody, Reference]]:
|
||||
"""Get the request body or err."""
|
||||
request_bodies = self._components_strict.requestBodies
|
||||
if request_bodies is None:
|
||||
raise ValueError("No request body found in spec. ")
|
||||
return request_bodies
|
||||
|
||||
def _get_root_referenced_parameter(self, ref: Reference) -> Parameter:
|
||||
"""Get the root reference or err."""
|
||||
parameter = self._get_referenced_parameter(ref)
|
||||
while isinstance(parameter, Reference):
|
||||
parameter = self._get_referenced_parameter(parameter)
|
||||
return parameter
|
||||
def _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_strict
|
||||
if ref_name not in parameters:
|
||||
raise ValueError(f"No parameter found for {ref_name}")
|
||||
return parameters[ref_name]
|
||||
|
||||
def get_referenced_schema(self, ref: Reference) -> Schema:
|
||||
"""Get a schema (or nested reference) or err."""
|
||||
ref_name = ref.ref.split("/")[-1]
|
||||
schemas = self._schemas_strict
|
||||
if ref_name not in schemas:
|
||||
raise ValueError(f"No schema found for {ref_name}")
|
||||
return schemas[ref_name]
|
||||
def _get_root_referenced_parameter(self, ref: Reference) -> Parameter:
|
||||
"""Get the root reference or err."""
|
||||
parameter = self._get_referenced_parameter(ref)
|
||||
while isinstance(parameter, Reference):
|
||||
parameter = self._get_referenced_parameter(parameter)
|
||||
return parameter
|
||||
|
||||
def get_schema(self, schema: Union[Reference, Schema]) -> Schema:
|
||||
if isinstance(schema, Reference):
|
||||
return self.get_referenced_schema(schema)
|
||||
return schema
|
||||
def get_referenced_schema(self, ref: Reference) -> Schema:
|
||||
"""Get a schema (or nested reference) or err."""
|
||||
ref_name = ref.ref.split("/")[-1]
|
||||
schemas = self._schemas_strict
|
||||
if ref_name not in schemas:
|
||||
raise ValueError(f"No schema found for {ref_name}")
|
||||
return schemas[ref_name]
|
||||
|
||||
def _get_root_referenced_schema(self, ref: Reference) -> Schema:
|
||||
"""Get the root reference or err."""
|
||||
schema = self.get_referenced_schema(ref)
|
||||
while isinstance(schema, Reference):
|
||||
schema = self.get_referenced_schema(schema)
|
||||
return schema
|
||||
def get_schema(self, schema: Union[Reference, Schema]) -> Schema:
|
||||
if isinstance(schema, Reference):
|
||||
return self.get_referenced_schema(schema)
|
||||
return schema
|
||||
|
||||
def _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_strict
|
||||
if ref_name not in request_bodies:
|
||||
raise ValueError(f"No request body found for {ref_name}")
|
||||
return request_bodies[ref_name]
|
||||
def _get_root_referenced_schema(self, ref: Reference) -> Schema:
|
||||
"""Get the root reference or err."""
|
||||
schema = self.get_referenced_schema(ref)
|
||||
while isinstance(schema, Reference):
|
||||
schema = self.get_referenced_schema(schema)
|
||||
return schema
|
||||
|
||||
def _get_root_referenced_request_body(
|
||||
self, ref: Reference
|
||||
) -> Optional[RequestBody]:
|
||||
"""Get the root request Body or err."""
|
||||
request_body = self._get_referenced_request_body(ref)
|
||||
while isinstance(request_body, Reference):
|
||||
request_body = self._get_referenced_request_body(request_body)
|
||||
return request_body
|
||||
def _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_strict
|
||||
if ref_name not in request_bodies:
|
||||
raise ValueError(f"No request body found for {ref_name}")
|
||||
return request_bodies[ref_name]
|
||||
|
||||
@staticmethod
|
||||
def _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")
|
||||
if isinstance(openapi_version, str):
|
||||
if openapi_version != "3.1.0":
|
||||
def _get_root_referenced_request_body(
|
||||
self, ref: Reference
|
||||
) -> Optional[RequestBody]:
|
||||
"""Get the root request Body or err."""
|
||||
request_body = self._get_referenced_request_body(ref)
|
||||
while isinstance(request_body, Reference):
|
||||
request_body = self._get_referenced_request_body(request_body)
|
||||
return request_body
|
||||
|
||||
@staticmethod
|
||||
def _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")
|
||||
if isinstance(openapi_version, str):
|
||||
if openapi_version != "3.1.0":
|
||||
logger.warning(
|
||||
f"Attempting to load an OpenAPI {openapi_version}"
|
||||
f" spec. {warning_message}"
|
||||
)
|
||||
else:
|
||||
pass
|
||||
elif isinstance(swagger_version, str):
|
||||
logger.warning(
|
||||
f"Attempting to load an OpenAPI {openapi_version}"
|
||||
f"Attempting to load a Swagger {swagger_version}"
|
||||
f" spec. {warning_message}"
|
||||
)
|
||||
else:
|
||||
pass
|
||||
elif isinstance(swagger_version, str):
|
||||
logger.warning(
|
||||
f"Attempting to load a Swagger {swagger_version}"
|
||||
f" spec. {warning_message}"
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Attempting to load an unsupported spec:"
|
||||
f"\n\n{obj}\n{warning_message}"
|
||||
)
|
||||
raise ValueError(
|
||||
"Attempting to load an unsupported spec:"
|
||||
f"\n\n{obj}\n{warning_message}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def parse_obj(cls, obj: dict) -> "OpenAPISpec":
|
||||
try:
|
||||
cls._alert_unsupported_spec(obj)
|
||||
return super().parse_obj(obj)
|
||||
except ValidationError as e:
|
||||
# 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)
|
||||
for error in e.errors():
|
||||
keys = error["loc"]
|
||||
item = new_obj
|
||||
for key in keys[:-1]:
|
||||
item = item[key]
|
||||
item.pop(keys[-1], None)
|
||||
return cls.parse_obj(new_obj)
|
||||
@classmethod
|
||||
def parse_obj(cls, obj: dict) -> OpenAPISpec:
|
||||
try:
|
||||
cls._alert_unsupported_spec(obj)
|
||||
return super().parse_obj(obj)
|
||||
except ValidationError as e:
|
||||
# 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)
|
||||
for error in e.errors():
|
||||
keys = error["loc"]
|
||||
item = new_obj
|
||||
for key in keys[:-1]:
|
||||
item = item[key]
|
||||
item.pop(keys[-1], None)
|
||||
return cls.parse_obj(new_obj)
|
||||
|
||||
@classmethod
|
||||
def from_spec_dict(cls, spec_dict: dict) -> "OpenAPISpec":
|
||||
"""Get an OpenAPI spec from a dict."""
|
||||
return cls.parse_obj(spec_dict)
|
||||
@classmethod
|
||||
def from_spec_dict(cls, spec_dict: dict) -> OpenAPISpec:
|
||||
"""Get an OpenAPI spec from a dict."""
|
||||
return cls.parse_obj(spec_dict)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, text: str) -> "OpenAPISpec":
|
||||
"""Get an OpenAPI spec from a text."""
|
||||
try:
|
||||
spec_dict = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
spec_dict = yaml.safe_load(text)
|
||||
return cls.from_spec_dict(spec_dict)
|
||||
@classmethod
|
||||
def from_text(cls, text: str) -> OpenAPISpec:
|
||||
"""Get an OpenAPI spec from a text."""
|
||||
try:
|
||||
spec_dict = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
spec_dict = yaml.safe_load(text)
|
||||
return cls.from_spec_dict(spec_dict)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Union[str, Path]) -> "OpenAPISpec":
|
||||
"""Get an OpenAPI spec from a file path."""
|
||||
path_ = path if isinstance(path, Path) else Path(path)
|
||||
if not path_.exists():
|
||||
raise FileNotFoundError(f"{path} does not exist")
|
||||
with path_.open("r") as f:
|
||||
return cls.from_text(f.read())
|
||||
@classmethod
|
||||
def from_file(cls, path: Union[str, Path]) -> OpenAPISpec:
|
||||
"""Get an OpenAPI spec from a file path."""
|
||||
path_ = path if isinstance(path, Path) else Path(path)
|
||||
if not path_.exists():
|
||||
raise FileNotFoundError(f"{path} does not exist")
|
||||
with path_.open("r") as f:
|
||||
return cls.from_text(f.read())
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url: str) -> "OpenAPISpec":
|
||||
"""Get an OpenAPI spec from a URL."""
|
||||
response = requests.get(url)
|
||||
return cls.from_text(response.text)
|
||||
@classmethod
|
||||
def from_url(cls, url: str) -> OpenAPISpec:
|
||||
"""Get an OpenAPI spec from a URL."""
|
||||
response = requests.get(url)
|
||||
return cls.from_text(response.text)
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
"""Get the base url."""
|
||||
return self.servers[0].url
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
"""Get the base url."""
|
||||
return self.servers[0].url
|
||||
|
||||
def get_methods_for_path(self, path: str) -> List[str]:
|
||||
"""Return a list of valid methods for the specified path."""
|
||||
path_item = self._get_path_strict(path)
|
||||
results = []
|
||||
for method in HTTPVerb:
|
||||
operation = getattr(path_item, method.value, None)
|
||||
if isinstance(operation, Operation):
|
||||
results.append(method.value)
|
||||
return results
|
||||
def get_methods_for_path(self, path: str) -> List[str]:
|
||||
"""Return a list of valid methods for the specified path."""
|
||||
path_item = self._get_path_strict(path)
|
||||
results = []
|
||||
for method in HTTPVerb:
|
||||
operation = getattr(path_item, method.value, None)
|
||||
if isinstance(operation, Operation):
|
||||
results.append(method.value)
|
||||
return results
|
||||
|
||||
def get_parameters_for_path(self, path: str) -> List[Parameter]:
|
||||
path_item = self._get_path_strict(path)
|
||||
parameters = []
|
||||
if not path_item.parameters:
|
||||
return []
|
||||
for parameter in path_item.parameters:
|
||||
if isinstance(parameter, Reference):
|
||||
parameter = self._get_root_referenced_parameter(parameter)
|
||||
parameters.append(parameter)
|
||||
return parameters
|
||||
|
||||
def get_operation(self, path: str, method: str) -> Operation:
|
||||
"""Get the operation object for a given path and HTTP method."""
|
||||
path_item = self._get_path_strict(path)
|
||||
operation_obj = getattr(path_item, method, None)
|
||||
if not isinstance(operation_obj, Operation):
|
||||
raise ValueError(f"No {method} method found for {path}")
|
||||
return operation_obj
|
||||
|
||||
def get_parameters_for_operation(self, operation: Operation) -> List[Parameter]:
|
||||
"""Get the components for a given operation."""
|
||||
parameters = []
|
||||
if operation.parameters:
|
||||
for parameter in operation.parameters:
|
||||
def get_parameters_for_path(self, path: str) -> List[Parameter]:
|
||||
path_item = self._get_path_strict(path)
|
||||
parameters = []
|
||||
if not path_item.parameters:
|
||||
return []
|
||||
for parameter in path_item.parameters:
|
||||
if isinstance(parameter, Reference):
|
||||
parameter = self._get_root_referenced_parameter(parameter)
|
||||
parameters.append(parameter)
|
||||
return parameters
|
||||
return parameters
|
||||
|
||||
def get_request_body_for_operation(
|
||||
self, operation: Operation
|
||||
) -> Optional[RequestBody]:
|
||||
"""Get the request body for a given operation."""
|
||||
request_body = operation.requestBody
|
||||
if isinstance(request_body, Reference):
|
||||
request_body = self._get_root_referenced_request_body(request_body)
|
||||
return request_body
|
||||
def get_operation(self, path: str, method: str) -> Operation:
|
||||
"""Get the operation object for a given path and HTTP method."""
|
||||
path_item = self._get_path_strict(path)
|
||||
operation_obj = getattr(path_item, method, None)
|
||||
if not isinstance(operation_obj, Operation):
|
||||
raise ValueError(f"No {method} method found for {path}")
|
||||
return operation_obj
|
||||
|
||||
@staticmethod
|
||||
def get_cleaned_operation_id(operation: Operation, path: str, method: str) -> str:
|
||||
"""Get a cleaned operation id from an operation id."""
|
||||
operation_id = operation.operationId
|
||||
if operation_id is None:
|
||||
# Replace all punctuation of any kind with underscore
|
||||
path = re.sub(r"[^a-zA-Z0-9]", "_", path.lstrip("/"))
|
||||
operation_id = f"{path}_{method}"
|
||||
return operation_id.replace("-", "_").replace(".", "_").replace("/", "_")
|
||||
def get_parameters_for_operation(self, operation: Operation) -> List[Parameter]:
|
||||
"""Get the components for a given operation."""
|
||||
parameters = []
|
||||
if operation.parameters:
|
||||
for parameter in operation.parameters:
|
||||
if isinstance(parameter, Reference):
|
||||
parameter = self._get_root_referenced_parameter(parameter)
|
||||
parameters.append(parameter)
|
||||
return parameters
|
||||
|
||||
def get_request_body_for_operation(
|
||||
self, operation: Operation
|
||||
) -> Optional[RequestBody]:
|
||||
"""Get the request body for a given operation."""
|
||||
request_body = operation.requestBody
|
||||
if isinstance(request_body, Reference):
|
||||
request_body = self._get_root_referenced_request_body(request_body)
|
||||
return request_body
|
||||
|
||||
@staticmethod
|
||||
def get_cleaned_operation_id(
|
||||
operation: Operation, path: str, method: str
|
||||
) -> str:
|
||||
"""Get a cleaned operation id from an operation id."""
|
||||
operation_id = operation.operationId
|
||||
if operation_id is None:
|
||||
# Replace all punctuation of any kind with underscore
|
||||
path = re.sub(r"[^a-zA-Z0-9]", "_", path.lstrip("/"))
|
||||
operation_id = f"{path}_{method}"
|
||||
return operation_id.replace("-", "_").replace(".", "_").replace("/", "_")
|
||||
|
||||
else:
|
||||
|
||||
class OpenAPISpec: # type: ignore[no-redef]
|
||||
"""Shim for pydantic version >=2"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
raise NotImplementedError("Only supported for pydantic version 1")
|
||||
|
@ -4,6 +4,18 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
# Keep at top of file to ensure that pydantic test can be skipped before
|
||||
# pydantic v1 related imports are attempted by openapi_schema_pydantic.
|
||||
from langchain import _PYDANTIC_MAJOR_VERSION
|
||||
|
||||
if _PYDANTIC_MAJOR_VERSION != 1:
|
||||
pytest.skip(
|
||||
f"Pydantic major version {_PYDANTIC_MAJOR_VERSION} is not supported.",
|
||||
allow_module_level=True,
|
||||
)
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from openapi_schema_pydantic import (
|
||||
|
Loading…
Reference in New Issue
Block a user