openapi3-parser icon indicating copy to clipboard operation
openapi3-parser copied to clipboard

[BUG]: recursive schema definition not work

Open thaichat04 opened this issue 1 year ago • 15 comments

Describe the bug A schema with recursive definition raised error

To Reproduce Steps to reproduce the behavior:

  1. Using sample schema as follow with recursive structure, allowed by spec: "definitions": { "models.Equipment": { "title": "Equipment", "type": "object", "properties": { "Features": { "type": "array", "items": { "$ref": "#/definitions/models.Feature" } }, "Id": { "type": "integer", "format": "int64" }, "IdType": { "type": "string" }, "Name": { "type": "string" }, "Price": { "type": "integer", "format": "int32" } } }, "models.Feature": { "title": "Feature", "type": "object", "properties": { "Equipments": { "type": "array", "items": { "$ref": "#/definitions/models.Equipment" } }, "Id": { "type": "integer", "format": "int64" }, "IdFeature": { "$ref": "#/definitions/models.Feature" }, "Name": { "type": "string" } } } }

Expected behavior Schema must be accepted, but there's error: openapi_parser.errors.ParserError: OpenAPI file parsing error: Recursion reached limit of 1 trying to resolve "file:///Users/nguyenh/Downloads/recursive_schema.json#/definitions/models.Feature"! file:///Users/nguyenh/Downloads/recursive_schema.json#/definitions/models.Feature file:///Users/nguyenh/Downloads/recursive_schema.json#/definitions/models.Equipment file:///Users/nguyenh/Downloads/recursive_schema.json#/definitions/models.Feature

System details (please complete the following information):

  • OS: [e.g. Windows 11, macOS 12.4, etc]
  • OpenAPI / Swagger version [e.g. OpenAPI 2]
  • Python version [e.g. 12]

thaichat04 avatar Nov 04 '24 11:11 thaichat04

Hey @thaichat04, thanks for opening this! This error comes from the prance module that is used for initial file parsing, I will check if the issue still persists in newer versions. If so, I will open an issue there, otherwise I will try to publish a new version soon.

manchenkoff avatar Nov 04 '24 13:11 manchenkoff

Thanks @manchenkoff, we are already on latest version of prance lib: Requirement already satisfied: prance in /usr/local/lib/python3.12/site-packages (23.6.21.0)

thaichat04 avatar Nov 04 '24 14:11 thaichat04

Thanks for confirmation @thaichat04, I see that there are some issues previously created in prance, I'll try to take a look if there is a solution or config to handle this. But most probably this recursion has to be stopped before reaching the limit somehow.

manchenkoff avatar Nov 04 '24 15:11 manchenkoff

Yes, but this one is still open from oct. 23: https://github.com/RonnyPfannschmidt/prance/issues/158

thaichat04 avatar Nov 04 '24 15:11 thaichat04

Yes, but this one is skill open from oct. 23: https://github.com/RonnyPfannschmidt/prance/issues/158

Oh, then we'll probably need to wait for that. Not sure that I'll be able to replace the resolver sooner.

manchenkoff avatar Nov 04 '24 15:11 manchenkoff

We can have a workaround like this:

class OpenAPIResolver:
    _resolver: prance.ResolvingParser

    @staticmethod
    def recursion_limit_handler(limit, parsed_url, recursions=()):
        """Ignore recursive."""
        path = []
        for rc in recursions:
            path.append("{}#/{}".format(rc[0], "/".join(rc[1])))
        path = "\n".join(path)
        printf(path)


    def __init__(self, uri: Optional[str], spec_string: Optional[str] = None) -> None:
        self._resolver = prance.ResolvingParser(
            uri,
            spec_string=spec_string,
            backend=OPENAPI_SPEC_VALIDATOR,
            strict=False,
            lazy=True,
            recursion_limit_handler = OpenAPIResolver.recursion_limit_handler,
        )

thaichat04 avatar Nov 04 '24 17:11 thaichat04

@thaichat04 do you mind opening a PR with your suggestion and a test to make sure that the produced schema is still valid to use in the project? Looks like I won't be able to test it sooner than in a couple of weeks 😞

manchenkoff avatar Nov 05 '24 21:11 manchenkoff

Just adding my findings here as well.

I'm working with the Semantic Kernel plugin for openapi in my use case.

I used:

parser = ResolvingParser(spec_string=openapi_spec, resolve_types = resolver.RESOLVE_FILES, strict=False, recursion_limit=10)
parsed_spec = parser.specification

and passed this to:

    kernel.add_plugin_from_openapi(
        plugin_name=plugin_name,
        openapi_parsed_spec=parsed_spec,
        execution_settings=OpenAPIFunctionExecutionParameters(
                # Determines whether payload parameter names are augmented with namespaces.
                # Namespaces prevent naming conflicts by adding the parent parameter name
                # as a prefix, separated by dots
                auth_callback=my_auth_callback,
                enable_payload_namespacing=True
        )
    )

This resolved the issue for me, with the Azure OpenAPI specs.

adamhockemeyer avatar Dec 19 '24 20:12 adamhockemeyer

@adamhockemeyer did the circular dependency got resolved for the Azure OpenAPI specs with semantic kernel with Prance resolver package ?

kkkumar2 avatar Feb 19 '25 08:02 kkkumar2

@adamhockemeyer can you explain exactly what you did? I have the same parsing error: Recursion reached limit of 1 trying to resolve...

DolevAdas avatar Mar 24 '25 08:03 DolevAdas

@adamhockemeyer can you explain exactly what you did? I have the same parsing error: Recursion reached limit of 1 trying to resolve...

Hi, did you try the example I posted above - it is exactly what I tried.

adamhockemeyer avatar Mar 24 '25 18:03 adamhockemeyer

Yes @adamhockemeyer , I tried I am also getting recursion error because I have definition which is referencing itself and prance is unable to resolve it. Can you paste ur openapi spec here

kkkumar2 avatar Mar 24 '25 18:03 kkkumar2

Yes @adamhockemeyer , I tried I am also getting recursion error because I have definition which is referencing itself and prance is unable to resolve it. Can you paste ur openapi spec here

https://raw.githubusercontent.com/Azure/azure-rest-api-specs/refs/heads/main/specification/maps/data-plane/Microsoft.Maps/Weather/preview/1.0/weather.json

adamhockemeyer avatar Mar 24 '25 20:03 adamhockemeyer

@adamhockemeyer can you add the imports and the full code? from where ResolvingParser , resolver.RESOLVE_FILES came from?

DolevAdas avatar Mar 25 '25 16:03 DolevAdas

save_openapi_spec = "weather.json"
from prance import ResolvingParser
from prance.util import resolver
# try:
#     parser = ResolvingParser(save_openapi_spec)
#     print(parser)
# except Exception as e:
#     print(e)

# parser = ResolvingParser(spec_string=save_openapi_spec, resolve_types = resolver.RESOLVE_FILES, strict=False, recursion_limit=10)
# parsed_spec = parser.specification
url = "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/refs/heads/main/specification/maps/data-plane/Microsoft.Maps/Weather/preview/1.0/weather.json"
# import requests
# resp = requests.get(url)
# if resp.status_code == 200:
#     parser = ResolvingParser(spec_string=resp.json(), resolve_types = resolver.RESOLVE_FILES, strict=False, recursion_limit=10)
#     parsed_spec = parser.specification

parser = ResolvingParser(url=url, resolve_types = resolver.RESOLVE_HTTP, strict=False, recursion_limit=10)
parsed_spec = parser.specification

print(parsed_spec)

@adamhockemeyer "I've tried passing the URL, filename, spec JSON, and spec text in all scenarios, but I am consistently encountering a recursion error. Can you help me identify what I might be missing?"

With Filename Image

With URL Image

kkkumar2 avatar Mar 25 '25 17:03 kkkumar2