Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with nested self-referencing schemas #813

Open
mouadennasri opened this issue Nov 25, 2022 · 2 comments
Open

Issue with nested self-referencing schemas #813

mouadennasri opened this issue Nov 25, 2022 · 2 comments
Labels

Comments

@mouadennasri
Copy link

mouadennasri commented Nov 25, 2022

I have an issue with nested self-referencing schema,

class GroupSchema(HasPermissionMixin, ModelSchema):
    content = fields.Nested(GroupParamSchema, required=True)

class GroupParamSchema(Schema):
    filters = fields.Nested(FilterSchema, required=True, many=True)

class FilterSchema(Schema):
    next_filter = fields.Nested("FilterSchema", required=False, allow_none=True)

The issue is with FilterSchema

I have a helper method that converts Marshmallow Schema to an OpenApiJson object:

def get_openapi_schema(
    serializer,
):
    spec = APISpec(
        title="",
        version="",
        openapi_version="3.0.2",
        plugins=[MarshmallowPlugin(schema_name_resolver=schema_name_resolver)],
    )

    openapi_schema = OpenAPIConverter(openapi_version="3.0.2",schema_name_resolver=schema_name_resolver,spec=spec)
    return {200: openapi_schema.schema2jsonschema(serializer)}

The schema_name_resolver as described in the docs should not return None for Circular schemas

def schema_name_resolver(schema):

    schema_name = resolve_schema_cls(schema).__name__
    circular = False
    values = list(schema.fields.values())

    for value in values:
        if value.__class__.__name__ == "Nested":
            if value.nested == schema_name:
                circular = True
                break

    if circular:
        return schema_name

    return None

But it still complains:

 File "/usr/local/lib/python3.9/site-packages/apispec/ext/marshmallow/openapi.py", line 297, in get_ref_dict
    ref_schema = self.spec.components.get_ref("schema", self.refs[schema_key])
KeyError: (<class 'veylinx.api.insights.serializers.base.FilterSchema'>, None, frozenset(), frozenset(), frozenset(), False)

And I'm sure that the schema_name_resolver is returning a string when the schema is circular

Using a resolver as lambda schema_class: None will raise the error below which is understandable!

apispec.exceptions.APISpecError: Name resolver returned None for schema <FilterSchema(many=False)> which is part of a chain of circular referencing schemas. Please ensure that the schema_name_resolver passed to MarshmallowPlugin returns a string for all circular referencing schemas.

I'm using:

Django==4.0.8
apispec==6.0.2
marshmallow==3.19.0
Debian GNU/Linux 11 (bullseye)
@lafrech
Copy link
Member

lafrech commented Nov 25, 2022

From a very quick look, I have the feeling you're comparing a schema class/instance and a name. Should it be something more like this?

if resolve_schema_cls(value.nested).__name__ == schema_name:

or perhaps even

if resolve_schema_cls(value.nested) == resolve_schema_cls(schema):

@mouadennasri
Copy link
Author

@lafrech thank you for the quick feedback! but sadly that didn't solve the issue, the ApiSpec.refs is empty and it raises:

    ref_schema = self.spec.components.get_ref("schema", self.refs[schema_key])
KeyError: (<class 'api.insights.serializers.base.FilterSchema'>, None, frozenset(), frozenset(), frozenset(), False)

These are some debug outputs:

(Pdb) self.refs
{}
(Pdb) schema_key
(<class 'api.insights.serializers.base.FilterSchema'>, None, frozenset(), frozenset(), frozenset(), False)
(Pdb) self.spec.components.to_dict()
{
    "schemas": {
        "FilterSchema": {
            "type": "object",
            "properties": {
                "next_filter": {
                    "nullable": True,
                    "allOf": [{"$ref": "#/components/schemas/FilterSchema"}],
                },
                "filter_type": {
                    "type": "string",
                    "enum": [
                        "Filter",
                        "IsFinishedFilter",
                    ],
                },
                "params": {
                    "type": "array",
                    "nullable": True,
                    "items": {"nullable": True},
                },
                "operator_class": {
                    "type": "string",
                    "enum": [
                        "QueryOp",
                        "AndOperator",
                    ],
                },
                "legacy_filter": {"type": "object"},
                "auction_id": {
                    "type": "array",
                    "nullable": True,
                    "items": {"type": "string"},
                },
                "filter_join": {
                    "type": "string",
                    "enum": [
                        "QueryOp",
                        "AndOperator"
                    ],
                },
            },
            "required": ["filter_type", "operator_class"],
        }
    }
}
(Pdb) self.spec.components.get_ref("schema", "FilterSchema")
{'$ref': '#/components/schemas/FilterSchema'}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants