pyopenapi / pyswagger

An OpenAPI (fka Swagger) client & converter in python, which is type-safe, dynamic, spec-compliant.
MIT License
385 stars 89 forks source link

Ambiguous error when loading from yaml example #149

Closed jcfrank closed 6 years ago

jcfrank commented 6 years ago

Hi,

This is probably not a pyswagger issue but I just want to report what I observe and maybe you can decide whether it is or not an issue.

Looking at the 'quick start' section, the sample code loads spec from

http://petstore.swagger.io/v2/swagger.json

My current project prefers YAML format, so naturally I tried letting pyswagger load from this alternative:

http://petstore.swagger.io/v2/swagger.yaml

Code:

app = pyswagger.App.load('http://petstore.swagger.io/v2/swagger.yaml')
app.prepare()

But then I got the following error when preforming prepare:

TypeError: 'int' object is not iterable

The json version can be prepared without problem, so I went on and compared their differences. The key issue was under paths section, responses part.

      responses:
        405:
          description: "Invalid input"
    "responses": {
      "405": {
        "description": "Invalid input"
      }
    },

In the YAML file the status codes were listed as numbers, but in JSON file they were strings. This will cause error even with strict set to False.

Adding double quotes in YAML solves the problem, but the file is from the same example source so there could be others like me, confused by this in the future.

mission-liao commented 6 years ago

@jcfrank Thanks for reporting issues. This part should handle this case:

It seems this part didn't do its job well, so it's a bug, :(

jcfrank commented 6 years ago

@mission-liao Thanks for pointing me to the code! It appears this error happens before such class is included.

The following stacktrace shows it happens at validate(strict=strict). While the YamlFixer is only used inside prepare_obj, which is called later.

Stacktrace:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-b2a656c09f14> in <module>()
----> 1 app.prepare()

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/core.pyc in prepare(self, strict)
    317         """
    318
--> 319         self.validate(strict=strict)
    320         self.__root = self.prepare_obj(self.raw, self.__url)
    321

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/core.pyc in validate(self, strict)
    303         """
    304
--> 305         result = self._validate()
    306         if strict and len(result):
    307             for r in result:

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/core.pyc in _validate(self)
    256         v = v_mod.Validate()
    257
--> 258         s.scan(route=[v], root=self.__raw)
    259         return v.errs
    260

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/scan.pyc in scan(self, route, root, nexter, leaves)
    109
    110         merged_r = self.__build_route(route)
--> 111         for path, obj in nexter(root, leaves):
    112             for the_self, r, res in merged_r:
    113

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/scan.pyc in default_tree_traversal(root, leaves)
     13         # to encode it again.
     14         if obj.__class__ not in leaves:
---> 15             objs.extend(map(lambda i: (path + '/' + i[0],) + (i[1],), six.iteritems(obj._children_)))
     16
     17         # the path we expose here follows JsonPointer described here

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/spec/base.pyc in _children_(self)
    470
    471         for n in names:
--> 472             down(jp_compose(n), getattr(self, n))
    473
    474         return ret

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/spec/base.pyc in down(name, obj)
    467             elif isinstance(obj, dict):
    468                 for k, v in six.iteritems(obj):
--> 469                     down(jp_compose(k, name), v)
    470
    471         for n in names:

/home/vagrant/.virtualenvs/someenv/local/lib/python2.7/site-packages/pyswagger-0.8.36-py2.7.egg/pyswagger/utils.pyc in jp_compose(s, base)
    236
    237     ss = [s] if isinstance(s, six.string_types) else s
--> 238     ss = [s.replace('~', '~0').replace('/', '~1') for s in ss]
    239     if base:
    240         ss.insert(0, base)

TypeError: 'int' object is not iterable
mission-liao commented 6 years ago

@jcfrank I just found that the test case to check this part only load the document and run the scanner to fix it. Therefore it failed when you call create like others do.

I think the validation can be moved to some place after prepare_obj. This bug would be fixed in next version.

For now, you can workaround this issue by calling the scanner before app.prepare(strict=True), like this:

app = App.load(.....)
s = Scanner(app)
s.scan(app.raw, route=[YamlFixer()], leaves=[Operaton])
app.prepare(strict=True)
jcfrank commented 6 years ago

@mission-liao Alright. Thanks again! Needs a little modification to the snippet, though. Otherwise it gets a TypeError: scan() got multiple values for keyword argument 'route'.

Updated:

from pyswagger import App
from pyswagger.scan import Scanner
from pyswagger.core import Operation
from pyswagger.scanner.v2_0.yaml import YamlFixer

app = App.load('http://petstore.swagger.io/v2/swagger.yaml')
s = Scanner(app)
s.scan(root=app.raw, route=[YamlFixer()], leaves=[Operation])
app.prepare(strict=True)