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

Wildcard field contains an extra nested level in docs #57

Open
anthiras opened this issue Feb 14, 2020 · 9 comments
Open

Wildcard field contains an extra nested level in docs #57

anthiras opened this issue Feb 14, 2020 · 9 comments
Labels
bug Something isn't working

Comments

@anthiras
Copy link

This might be related to the recent wildcard field changes, e.g. #24.

When using a wildcard field with nested models, the resulting documentation contains three levels: *, <*>, and the nested model, where I would expect just two: The key <*> and the nested model. Example values in the doc also contains an extra nested level.

Please note, the actual output of the marshaled model in get is correct, only the swagger doc is wrong.

Code

from flask import Flask
from flask_restx import Resource, Api, fields

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

nested_model = api.model('Nested model', {'nested_string': fields.String()})
wild = fields.Wildcard(fields.Nested(nested_model))
wildcard_model = api.model('Wildcard model', {'*': wild})

@api.route('/hello')
class HelloWorld(Resource):
    @api.marshal_with(wildcard_model)
    def get(self):
        return {
            'foo': {
                'nested_string': 'foo'
            },
            'bar': {
                'nested_string': 'bar'
            }
        }

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

Repro Steps (if applicable)

  1. Run example above
  2. View documentation at http://localhost:5000

Actual model doc

wildcard-model

Expected model doc

Only one level of *, not two.

Actual example value doc

{
  "*": {
    "additionalProp1": {
      "nested_string": "string"
    },
    "additionalProp2": {
      "nested_string": "string"
    },
    "additionalProp3": {
      "nested_string": "string"
    }
  }
}

Expected example value doc

{
  "additionalProp1": {
    "nested_string": "string"
  },
  "additionalProp2": {
    "nested_string": "string"
  },
  "additionalProp3": {
    "nested_string": "string"
  }
}

or

{
  "*": {
    "nested_string": "string"
  }
}

Actual output of get (correct)

{
  "bar": {
    "nested_string": "bar"
  },
  "foo": {
    "nested_string": "foo"
  }
}

Environment

  • Python 3.7
  • Flask 1.1.1
  • Flask-RESTX 0.1.1
@anthiras anthiras added the bug Something isn't working label Feb 14, 2020
@ziirish
Copy link
Contributor

ziirish commented Feb 17, 2020

Hello,

I'm not sure this is related to #24.
The "double-nested" level is kind of expected (or at least it's how Wildcard fields have been initially implemented).

Here is the initial PR that introduced this feature: noirbizarre/flask-restplus#255 (comment)

As you can see, I already questioned about the resulting swagger, but at that time, nobody reacted to this limitation.

@SteadBytes, @j5awry do you think we could improve the Wildcard representation in the swagger?

@Gaasmann
Copy link

Hello @ziirish ,
When you said "kind of expected", I'm not sure if you mean that wildcards weren't meant for @anthiras' usecase or not. I have the same issue where I'd like to have:

{
  "field1": "value1",
  "nested_and_dynamic_keys": {
    "wildcard_field1": {
      "foo": "bar",
      "alice": "bob"
    },
    "wildcard_field2": {
      "foo": "rba",
      "alice": "bbo"
    }
  }
}

But I got this instead:

{
  "field1": "value1",
  "nested_and_dynamic_keys": {
    "*": {
      "wildcard_field1": {
        "foo": "bar",
        "alice": "bob"
      },
      "wildcard_field2": {
        "foo": "rba",
        "alice": "bbo"
      }
    }
  }
}

Is there a way to get the first result or is it really a bugfix/new feature?

@esztermarton
Copy link

I also wanted this. I could get it to display how I wanted by taking out the Nested model, and having the wildcard field being referenced directly - but this functionally was wrong. So I ended up just being okay with the display looking looking a little off. Nevertheless if this is resolved that would be cool! :)

for me, model definition:

from flask import Flask
from flask_restx import Resource, Api, fields

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

wild = {"*": fields.Wildcard(fields.String())}
final_model = api.model(
    'Final model', {"field1": fields.String(), "nested_and_dynamic_keys": fields.Nested(wild)}
)

@api.route('/hello')
class HelloWorld(Resource):
    @api.marshal_with(final_model)
    def get(self):
        return {
            'foo': {
                'nested_string': 'foo'
            },
            'bar': {
                'nested_string': 'bar'
            }
        }

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

desired output:

{
  "field1": "value1",
  "nested_and_dynamic_keys": {
      "wildcard_field1": "foo"
      "wildcard_field2": "bar"
  }
}

actual output:

{
  "field1": "value1",
  "nested_and_dynamic_keys": {
    "*": {
    "wildcard_field1": "foo"
    "wildcard_field2": "bar"
  }
  }
}

@ziirish
Copy link
Contributor

ziirish commented Mar 3, 2020

When you said "kind of expected", I'm not sure if you mean that wildcards weren't meant for @anthiras' usecase or not.

Actually, I didn't find yet a swagger definition that matches a model with wildcard fields.
That's the reason why the generated schema may look strange.

@j5awry
Copy link
Contributor

j5awry commented Mar 6, 2020

I would expect a wildcard to be a pattern. https://swagger.io/specification/#schemaObject In the yaml style:

      username:
        type: string
        pattern: "[a-z0-9]{8,64}"
        minLength: 8
        maxLength: 64

So a wildcard in a nested field would be:

{
    "field1": "value1",
    "nested_and_dynamic_keys": {
        "variable_name": {
            "pattern": "regex",
         },
         "variable_name": {
            "pattern": "regex"
         }
    }
}

I don't have access to some of the examples I made in the past at the moment...and sadly won't for roughly a month. I'll try and mock this up

@FloLaco
Copy link

FloLaco commented Jul 21, 2020

@ziirish Here is an example of swagger definition for wildcard :

swagger: '2.0'
paths:
  '/ipam/{subnet}-{mask}':
    get:
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/document%20download%20response'
        '400':
          description: Failed. Bad post data.
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
      description: Request the number of available IPv4 by subnet/mask
      operationId: get_add
      parameters:
        - name: subnet
          in: path
          required: true
          type: string
          description: 'IP Network address ex: 192.168.10.0'
        - name: mask
          in: path
          required: true
          type: string
          description: 'Mask bits ex: 24'
      tags:
        - ipam
info:
  title: Network and Security REST API
  version: v0.7.3
  description: Network and Security REST API
  contact:
    name: Florian Lacommare
produces:
  - application/json
consumes:
  - application/json
definitions:
  document download response:
    properties:
      free_ip_number:
        type: string
        description: Number of IPv4 available in a subnet.
      free_ip_percent:
        type: string
        description: Percentage of IP addresses available in a subnet.
      ip_list:
        description: Dictionary with ips as key and properties of the ip as value.
        type: object
        additionalProperties:
          $ref: '#/definitions/IP'
        example: 
          ip1:
            dns_name: test
            alias: test
            type: test
            requester: test
            description: test
            environment: test
          ip2:
            dns_name: test
            alias: test
            type: test
            requester: test
            description: test
            environment: test
    type: object
  IP:
    properties:
      dns_name:
        type: string
        description: Domain name
        example: frsvp.com
      alias:
        type: string
        description: alias
        example: test1.com
      type:
        type: string
        description: Type of equipment
      requester:
        type: string
        description: The firstname_lastname of the requester
      description:
        type: string
        description: 'The description of your IP (server name, project, etc)'
      environment:
        type: string
        description: Environment type
    type: object

The example output show in Swagger :

{
  "free_ip_number": "string",
  "free_ip_percent": "string",
  "ip_list": {
    "ip1": {
      "dns_name": "test",
      "alias": "test",
      "type": "test",
      "requester": "test",
      "description": "test",
      "environment": "test"
    },
    "ip2": {
      "dns_name": "test",
      "alias": "test",
      "type": "test",
      "requester": "test",
      "description": "test",
      "environment": "test"
    }
  }
}

image

@syzhakov
Copy link

syzhakov commented Oct 1, 2020

It seems like wildcards can't live in the root level of the object with api.model()... Wish it would be added soon...

@Nantero1
Copy link

Nantero1 commented Apr 20, 2021

@ziirish I suffer from this behavior as well and in my opinion this is clearly a bug as @FloLaco showed. Shall I make a PR which "fixes" this? Maybe with a new default pre-set parameter like nested=True for backwards compatibility reasons? Or is this intended behavior and a "feature" and no "fix" is welcomed?

@lennart-bader
Copy link

Is there some solution / workaround for this issue?

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

9 participants