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

Unable to use models created with @api.schema_model with @api.expect if they have nested objects #494

Closed
poopledc opened this issue Dec 6, 2022 · 2 comments
Labels
bug Something isn't working

Comments

@poopledc
Copy link

poopledc commented Dec 6, 2022

As the title says, unable to use json schemas to properly render the swagger documentation. I am using the example from the link found here: https://flask-restx.readthedocs.io/en/latest/marshalling.html#define-model-using-json-schema to load in a JSON schema with a nested reference. Then I use the @api.expect decorator to add the Person object to the GET method. I load up the SwaggerUI at http://localhost:5000 and there are Swagger errors present

Code

from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app)



address = api.schema_model('Address', {
    'properties': {
        'road': {
            'type': 'string'
        },
    },
    'type': 'object'
})

person = api.schema_model('Person', {
    'required': ['address'],
    'properties': {
        'name': {
            'type': 'string'
        },
        'age': {
            'type': 'integer'
        },
        'birthdate': {
            'type': 'string',
            'format': 'date-time'
        },
        'address': {
            '$ref': '#/definitions/Address',
        }
    },
    'type': 'object'
})

@api.route("/")
class TestResource(Resource):
    @api.expect(person)
    def get(self):
        return {"hello": "world"}

if __name__ == "__main__":
    app.run(debug=True)

Here is the Swagger Doc produced:
image

Here is the swagger.json produced

{
    "swagger": "2.0",
    "basePath": "\/",
    "paths": {
        "\/": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "operationId": "get_test_resource",
                "parameters": [
                    {
                        "name": "payload",
                        "required": true,
                        "in": "body",
                        "schema": {
                            "$ref": "#\/definitions\/Person"
                        }
                    }
                ],
                "tags": [
                    "default"
                ]
            }
        }
    },
    "info": {
        "title": "API",
        "version": "1.0"
    },
    "produces": [
        "application\/json"
    ],
    "consumes": [
        "application\/json"
    ],
    "tags": [
        {
            "name": "default",
            "description": "Default namespace"
        }
    ],
    "definitions": {
        "Person": {
            "required": [
                "address"
            ],
            "properties": {
                "name": {
                    "type": "string"
                },
                "age": {
                    "type": "integer"
                },
                "birthdate": {
                    "type": "string",
                    "format": "date-time"
                },
                "address": {
                    "$ref": "#\/definitions\/Address"
                }
            },
            "type": "object"
        }
    },
    "responses": {
        "ParseError": {
            "description": "When a mask can't be parsed"
        },
        "MaskError": {
            "description": "When any error occurs on mask"
        }
    }
}

Repro Steps (if applicable)

  1. Create basic Flask app using flask and flask-restx
  2. Create models using api.schema_model
  3. Create a test endpoint using @api.route and Resource
  4. Create a GET method
  5. Add @api.expect(schema_model)
  6. Run Flask app
  7. Open web browser and navigate to http://localhost:5000 to get to SwaggerUI
  8. Expand Swagger definitions and see errors

Expected Behavior

No errors and the address field should populate properly under the payload section

Actual Behavior

Errors are thrown and the address field is listed as as string

Error Messages/Stack Trace

Resolver error at definitions.Person.properties.address.$ref
Could not resolve reference: Could not resolve pointer: /definitions/Address does not exist in document
Resolver error at paths./.get.parameters.0.schema.properties.address.$ref
Could not resolve reference: Could not resolve pointer: /definitions/Address does not exist in document

Environment

  • Python version: 3.9
  • Flask version: 2.1.3
  • Flask-RESTX version: 1.0.3
  • Other installed Flask extensions
@poopledc poopledc added the bug Something isn't working label Dec 6, 2022
@poopledc
Copy link
Author

poopledc commented Dec 7, 2022

OK so I tried out the same example using api.model and it worked. Code below

import restx_monkey as monkey

monkey.patch_restx()
from flask import Flask
from flask_restx import Api, Resource, fields

app = Flask(__name__)
api = Api(app)

address = api.model("Address", {"road": fields.String(required=False)})

person = api.model(
    "Person",
    {
        "address": fields.Nested(address, required=True),
        "name": fields.String(required=False),
        "age": fields.Integer(required=False),
        "birthdate": fields.DateTime(required=False),
    },
)


@api.route("/")
class TestResource(Resource):
    @api.expect(person)
    def get(self):
        return {"hello": "world"}


if __name__ == "__main__":
    app.run(debug=True)

And here's the Swagger page:
image

And here's there swagger.json

{
    "swagger": "2.0",
    "basePath": "\/",
    "paths": {
        "\/": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "operationId": "get_test_resource",
                "parameters": [
                    {
                        "name": "payload",
                        "required": true,
                        "in": "body",
                        "schema": {
                            "$ref": "#\/definitions\/Person"
                        }
                    }
                ],
                "tags": [
                    "default"
                ]
            }
        }
    },
    "info": {
        "title": "API",
        "version": "1.0"
    },
    "produces": [
        "application\/json"
    ],
    "consumes": [
        "application\/json"
    ],
    "tags": [
        {
            "name": "default",
            "description": "Default namespace"
        }
    ],
    "definitions": {
        "Person": {
            "required": [
                "address"
            ],
            "properties": {
                "name": {
                    "type": "string"
                },
                "age": {
                    "type": "integer"
                },
                "birthdate": {
                    "type": "string",
                    "format": "date-time"
                },
                "address": {
                    "$ref": "#\/definitions\/Address"
                }
            },
            "type": "object"
        },
        "Address": {
            "properties": {
                "road": {
                    "type": "string"
                }
            },
            "type": "object"
        }
    },
    "responses": {
        "ParseError": {
            "description": "When a mask can't be parsed"
        },
        "MaskError": {
            "description": "When any error occurs on mask"
        }
    }
}

So it looks like the Address model isn't added to the "definitions" block using the api.schema_model call, whereas the api.model call adds it.

@poopledc
Copy link
Author

poopledc commented Dec 8, 2022

OK I think this is the solution:
#59 (comment)

app["RESTX_INCLUDE_ALL_MODELS"] = True must be added

import restx_monkey as monkey
monkey.patch_restx()
from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app)
app.config["RESTX_INCLUDE_ALL_MODELS"] = True



address = api.schema_model('Address', {
    'properties': {
        'road': {
            'type': 'string'
        },
    },
    'type': 'object'
})

person = api.schema_model('Person', {
    'required': ['address'],
    'properties': {
        'name': {
            'type': 'string'
        },
        'age': {
            'type': 'integer'
        },
        'birthdate': {
            'type': 'string',
            'format': 'date-time'
        },
        'address': {
            '$ref': '#/definitions/Address',
        }
    },
    'type': 'object'
})

@api.route("/")
class TestResource(Resource):
    @api.expect(person)
    def get(self):
        return {"hello": "world"}

if __name__ == "__main__":
    app.run(debug=True)

@poopledc poopledc closed this as completed Dec 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant