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

Feature request - friendlier API for calling operations #56

Open pwfff opened 8 years ago

pwfff commented 8 years ago

I've written a wrapper for working with pyswagger that makes it easier to call operations. Maybe someday you can include some of this to make calling a simple operation a bit easier. It has a lot of magic in it (the exec is ugly, I know), but it's the best way I could find to get functions with the right signature.

import functools
from pyswagger import SwaggerApp, SwaggerSecurity
from pyswagger.contrib.client.requests import Client

OP_FUNC = '''def op_func(client, op, override_url, {}):
    kwargs = locals()
    del kwargs['client']
    del kwargs['op']
    del kwargs['override_url']

    return client.request(
        op(**kwargs),
        opt={{'url_netloc': override_url}},
    )'''

class SwaggerWrapper(object):
    def __init__(self, swagger_path, oauth_token='', override_url=''):
        self.app = SwaggerApp._create_(swagger_path)

        if oauth_token:
            auth = SwaggerSecurity(self.app)
            auth.update_with('api_token', oauth_token)
            self.client = Client(auth)
        else:
            self.client = Client()

        self.override_url = override_url

        for op in self.app.op.values():
            exec(OP_FUNC.format(', '.join(p.name for p in op.parameters)))

            op_func = functools.partial(op_func, self.client, op, self.override_url)

            op_func.__name__ = op.operationId
            op_func.__doc__ = op.description
            setattr(self, op.operationId, op_func)

It's made just for my use case (single API token), but having the function signature be based off of the docs is helpful. It also has the docstring on it based off of the description from the swagger document (I suppose I should also have it fall back to summary if description is missing).

In the example below I can just call c.post_import(some_data=data) and it returns the response. I find this a lot easier than calling the operation to get the request/response tuple, and then calling client.

In [1]: from swagger_wrapper import SwaggerWrapper
In [2]: c = SwaggerWrapper('http://swagger.dev.mysite.com/v1.yaml', 'mytoken123', 'my.local.instance:8003')

In [3]: c.post_import?
Type:            partial
String form:     <functools.partial object at 0x111d2be68>
File:            ~/.pyenv/versions/2.7.10/lib/python2.7/functools.py
Signature:       c.post_import(import_definition, property_id)
Docstring:
This is the description of the operation from the swagger doc.
Class docstring:
partial(func, *args, **keywords) - new function with partial application
of the given arguments and keywords.
pwfff commented 8 years ago

Also, some problems with my current implementation:

mission-liao commented 8 years ago

Hi,

I just checked your code and felt surprised by the usage of exec, it's magic. Right now the design of pyswagger focus on correctness first, then is usage friendly. In your case, the code might not work when operationId is not assigned (it's an optional field of Operation object)

for op in self.app.op.values():

I will suggest you to try to create a scanner to collect Operation objects to your dict, indexed by any mean your like. Refer to this issue, an example implementation is there.