pyopenapi / pyswagger

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

Add preference for application/json #112

Closed danizen closed 7 years ago

danizen commented 7 years ago

This issue is related to #87 - it would be nice if pyswagger worked out of the box with all of the petstore API. Even a simple get is flumoxed by the lack of either:

My preference would be to be able to add a header such as "Accept" "application/json" in the client. However, I guess I'm not seeing how to do that.

danizen commented 7 years ago

I will see if I can do this with security on "Accept", and see if it ends up in the header and converts the response to JSON.

mission-liao commented 7 years ago

For adding preference on "Accept", "Content-Type" section in header when requesting:

req, resp = app.op['your_operation_id'](**your_parameter')
req.produce('applications/json') # Accept: application/json
req.consume('application/xml') # Content-Type: application/json

A tutorial relative to this part is here

I didn't include xml coder in this project, so if you need to handle xml content-type, you need to install one and write a codec handler by yourself (sample code in tutorial already did it)

I'm not quite sure about your sentence: [...with security on "Accept"], thus if what I provided is not what you want, could you help to describe your scenario more detailedly?

danizen commented 7 years ago

Thanks, I'll try this.

danizen commented 7 years ago

So, I followed part of the mime wiki tutorial, and my XmlCodec doesn't work. However, that's OK, as what I want to do is simply add an Accept header and therefore get JSON instead. When I try

req.produce("application/json")

I get then a backtrace when I try to use it with a pyswagger.contrib.client.requests.Client object.

Since I'm posting a rather long backtrace, I should also mention that I've got 0.8.27 of pyswagger. Here is the rather long ipython trace:

In [20]: req, resp = app.op['findPetsByStatus'](status=['available'])

In [21]: req2 = req.produce('application/json')

In [22]: id(req2)
Out[22]: 140085523900512

In [23]: id(req)
Out[23]: 140085523900512

In [24]: r = client.request((req,resp,))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-0b77ba45e9e3> in <module>()
----> 1 r = client.request((req,resp,))

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/contrib/client/requests.py in request(self, req_and_resp, opt)
     72             status=rs.status_code,
     73             header=rs.headers,
---> 74             raw=six.BytesIO(rs.content).getvalue()
     75         )
     76 

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/io.py in apply_with(self, status, raw, header)
    401                 name = schema.name
    402                 data = self.__op._mime_codec.unmarshal(content_type, self.raw, _type=_type, _format=_format, name=name)
--> 403                 self.__data = r.schema._prim_(data, self.__op._prim_factory, ctx=dict(read=True))
    404 
    405         return self

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/spec/v2_0/objects.py in _prim_(self, v, prim_factory, ctx)
     90 
     91     def _prim_(self, v, prim_factory, ctx=None):
---> 92         return prim_factory.produce(self, v, ctx)
     93 
     94 

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/__init__.py in produce(self, obj, val, ctx)
    180             ret = creater(obj, val, ctx)
    181             if _2nd:
--> 182                 val = _2nd(obj, ret, val, ctx)
    183                 ctx['2nd_pass'] = _2nd
    184         elif len(obj.properties) or obj.additionalProperties:

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/comm.py in _2nd_pass_obj(obj, ret, val, ctx)
     38 #
     39 def _2nd_pass_obj(obj, ret, val, ctx):
---> 40     return ret.apply_with(obj, val, ctx)
     41 
     42 def create_obj(obj, v, ctx=None, constructor=None):

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/_array.py in apply_with(self, obj, val, ctx)
     35 
     36         if obj.items and len(val):
---> 37             self.extend(map(functools.partial(ctx['factory'].produce, obj.items), val))
     38             val = []
     39 

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/__init__.py in produce(self, obj, val, ctx)
    180             ret = creater(obj, val, ctx)
    181             if _2nd:
--> 182                 val = _2nd(obj, ret, val, ctx)
    183                 ctx['2nd_pass'] = _2nd
    184         elif len(obj.properties) or obj.additionalProperties:

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/comm.py in _2nd_pass_obj(obj, ret, val, ctx)
     38 #
     39 def _2nd_pass_obj(obj, ret, val, ctx):
---> 40     return ret.apply_with(obj, val, ctx)
     41 
     42 def create_obj(obj, v, ctx=None, constructor=None):

/home/dan/python-envs/trypyswagger/lib/python3.5/site-packages/pyswagger/primitives/_model.py in apply_with(self, obj, val, ctx)
     47         not_found = set(obj.required) - set(six.iterkeys(self))
     48         if len(not_found):
---> 49             raise ValueError('requirement not meet: {0}'.format(not_found))
     50 
     51         # remove assigned properties to avoid duplicated

ValueError: requirement not meet: {'name'}

In [25]: 
danizen commented 7 years ago

OK - I have some reason to think that this was not your problem, but the sort of assertion I actually want to use pyswagger to test. When I try "findPetsByStatus" with status=['available'] I get the failure on {'name'}. When I try "getPetById", it just works, and similarly for "findPetsByStatus" with status=['sold'].

My conclusion therefore is that the "requirement not met" message is because there is some pet out there without a name, and that name is required in the swagger.yaml So, the response truly was invalid.

Here is it working:

In [36]: req, resp = app.op['getPetById'](petId=3048819905)

In [37]: req.produce('application/json')
Out[37]: <pyswagger.io.Request at 0x7f4ea8dc15f8>

In [38]: r = client.request((req,resp,))

In [39]: r.status
Out[39]: 200

In [40]: r.data
Out[40]: 
{'category': {'id': 721582874, 'name': 'dog'},
 'id': 3048819905,
 'name': 'hello kity',
 'photoUrls': ['http://foo.bar.com/1', 'http://foo.bar.com/2'],
 'status': 'sold',
 'tags': [{'id': 168551466, 'name': 'swagger-codegen-python-pet-tag'}]}
In [41]: req.header
Out[41]: {'Accept': 'application/json'}

FWIW - it is req.produce('application/json') that sets Accept.

mission-liao commented 7 years ago

Not sure if I fully understand your intension, but to test findPetsByStatus, you need to provide 'string', not 'list of string'.

# correct
status='available'
# wrong
status=['available']
danizen commented 7 years ago

Thanks for writing pyswagger! Now that I've completed this tiny experiment, https://github.com/danizen/trypyswagger, I can show it to the guy at work who wants to use json-schema.org for validation and show him that this is better for our use case.

It definitely is an array of strings, as the following excerpt of petstore.yaml shows.

      parameters:
      - name: "status"
        in: "query"
        description: "Status values that need to be considered for filter"
        required: true
        type: "array"
        items:
          type: "string"
          enum:
          - "available"
          - "pending"
          - "sold"
          default: "available"
        collectionFormat: "multi"
mission-liao commented 7 years ago

got it, I'm wrong with that part :(

For json-schema validation, you can also refer to this project: https://github.com/Julian/jsonschema, which is highly recommended if you just want to use json-schema

The reason pyswagger didn't use that project to is Swagger (OpenAPI) is not fully compatible with json-schema, so I had to write one by myself.

danizen commented 7 years ago

Thanks for the pointer - both I and the other guy want to stay all Python on the server side, except for a tiny bit of npm/gulp/webpack for managing assets.

mission-liao commented 7 years ago

@danizen it's actually a project with 1000+ star in python :)