hug icon indicating copy to clipboard operation
hug copied to clipboard

Auto generate OpenAPI specification (fka The Swagger Specification)

Open wing328 opened this issue 8 years ago • 27 comments

To auto generate OpenAPI specification (https://github.com/OAI/OpenAPI-Specification)

wing328 avatar Feb 20 '16 10:02 wing328

This could be neatly implemented as a plugin for apispec, which is a pluggable API schema generator that supports the OpenAPI spec.

As an added bonus, apispec already has a plugin for marshmallow, so auto-generating OpenAPI entities from hug type annotations could be a breeze.

sloria avatar Mar 19 '16 15:03 sloria

@timothycrosley I was planning to write this, and open a pull request. However, would you suggest that this is implemented as a hug extension, instead, or should the api.HTTPInterfaceAPI.documentation be extended, as proposed here?

Though, an apispec plugin sounds interesting too.

prashnts avatar Apr 12 '16 17:04 prashnts

Hi @PrashntS, I think the best path forward is to create an extension for it, that reads HTTPInterfaceAPI.documentation to create the eventual output format. For a very simple example of how this might look you can see the hug_yaml implementation here: https://github.com/timothycrosley/hug_yaml/blob/develop/hug_yaml/not_found.py#L27

https://github.com/timothycrosley/hug_yaml

Which simply outputs the build in documentation using the YAML format. If you need more information to be present in the documentation in order for an alternative output format to be possible, that is where a pull-request against the core API would be useful.

Thanks!

~Timothy

timothycrosley avatar Apr 12 '16 17:04 timothycrosley

Sounds good @timothycrosley :smile:! I think a possible usage could be through something like spec parameter, to be explicit.

@hug.not_found(output=hug_yaml.output)
def documentation(hug_api, hug_api_version):
    return hug_yaml.not_found.documentation(hug_api, hug_api_version, spec='swagger')

prashnts avatar Apr 12 '16 18:04 prashnts

I'm looking into converting a project from Flask to Hug and would need support for for Marshmallow's apispec. What's the status on this and can I help out?

Thanks!

incognick avatar Jul 05 '16 16:07 incognick

@timothycrosley Hug is very impressive. But not supporting OpenAPI/Swagger documentation out of the box is (at least in my mind) a strike against it. Does the current documentation feature adhere to any spec standard? It is very similar to Swagger and might be trivial to adapt it to a strict Swagger spec. Would be willing to assist on this front as well.

kolanos avatar Jul 07 '16 16:07 kolanos

@timothycrosley -- I've been hacking away at this today using hug's documentation output...

One challenge with this approach is that supporting parameters and outputs gets difficult to do without having access to the original objects (and only have their docstrings). For example, when we have complex types like in_range we need a way to include the bounds in the documentation API for use in the swagger docs.

One approach to solving this would be to allow have documentation strings be parsable as YAML (Apispec has a helper method for this). Basically, we'd require some sort of new documentation format to express the extra information about our types. I do not see how marshmallow fields could be supported as first class types in this situation as we don't have control over their docstrings.

Having access to the original schemas in the case of marshmallow would make it possible to leverage the already existing extension to apispec. Without the original objects, some metaprogramming trickery will have to be involved to load them up.

Another alternative would be to add a documentation method to swagger that does not serialize input and output types to their docstring but leaves them as python objects.

Thoughts?


Also, after looking a bit more closely at Apispec's existing APIs it seems like something like the following would be a more appropriate way to hook into the library:

@hug.not_found()
def handle_404(request, hug_api):
    http = hug_api.http
    spec = APISpec(
        title="Title (can get from overview field in JSON)",
        version="Version (can get from version field in JSON)",
        plugins=[
            'apispec.ext.marshmallow',
        ]
    )

    for url, methods in http.routes.items():
        for method, method_versions in methods.items():
            for version, handler in method_versions.items():
                spec.add_path(hug_handler=handler)

    return spec.to_dict()

# Note: Complete pseudocode
# sample add_path hook
def add_path(hug_handler):
    # TODO: Builds up Path object based on handler (might need to create a com
    # TODO: If there are marshmallow schemas, can self.definition("SchemaName", schema=Schema)

Thoughts on something like that?

philipbjorge avatar Oct 30 '16 20:10 philipbjorge

@timothycrosley Like @philipbjorge, I think that working from documentation output is too far from the original object and forces us to implement a custom parser to get to the data.

@philipbjorge @sloria I've worked on implementing a plugin for apispec to support hug handlers a bit and got something working however I met some problems. apispec requires we add paths. We can extend it with a helper to load path and operations from an handler but in hug(with version support) we can have multiple path bound to a single handler. So which one do I choose ?

Also, when it came to documenting operations, I also resorted to using doc string. I wanted to push a little further and try to generate spec for parameters as well. Hug supports annotations so it should be easy. Here are some problems:

  • If a field is of type marshmallow, I can't assume the marshmallow apispec plugin is also configured, I could look for it though but if it's not loaded, I have to skip that field otherwise I'll end up re-doing the marshmallow plugin and it is already great so there is no point to that.
  • hug's types do not support meta data so I can't add specific documentation to them so generated spec would only be parameter name and type. I could work around that by creating new hug types for every parameter but this is counter productive.

As it is, my plugin is also tightly coupled to hug's internals in order to retrieve routes, methods and parameters from handler function so the plugin could break easily.

So I've pretty much came full circle and think it would be better to implement a format in documentation like @prashnts first suggested. It could use apispec under the hood to leverage marshmallow plugin and this way it would be easy to add all routes from a single handler.

@sloria I'd like to be proven wrong with my current plugin implementation and be able to fully support hug in apispec. If you think that binding hug plugin to marshmallow one is ok and we can find a way to add multiple path from a single handler, I think it could work granted it would still be tightly coupled to hug's internal.

What do you all think ?

EricFortin avatar Jan 25 '17 14:01 EricFortin

This would be a killer feature if hug could support this. Was there any additional work done on here? Thanks!

AlJohri avatar May 28 '17 12:05 AlJohri

A plus one on this feature request - if this could output Swagger then it could integrate well with the AWS API Gateway.

ghost avatar Jun 13 '17 08:06 ghost

+1.

nikhilalmeida avatar Jun 13 '17 22:06 nikhilalmeida

+1

dsmurrell avatar Jun 19 '17 12:06 dsmurrell

+1

hnykda avatar Aug 17 '17 15:08 hnykda

+1

zikphil avatar Aug 30 '17 18:08 zikphil

+1

mounte avatar Sep 04 '17 12:09 mounte

agreed this would be very useful

Imaclean74 avatar Sep 16 '17 04:09 Imaclean74

+1

timur-orudzhov avatar Oct 26 '17 11:10 timur-orudzhov

+1 ;)

ahirner avatar Oct 29 '17 10:10 ahirner

Probably not a good idea to generate it from documentation, and semi-auto would be great too.

JJ avatar Oct 30 '17 08:10 JJ

After two evening of light programming i did ugly solution for generation swagger spec. Right now it's draft and working only for marshmallow fields/types and schemas. For supporting hug.types needed to add some extra methods/fields to them.

gist: https://gist.github.com/nonamenix/cbec59644eacc7661c86b5caa75cfb7e

@hug.get('/swagger/{swagger_types_number}/', versions=[2, 3])
@swagger.response(200, description='Good response', schema=TestingSchema)
@swagger.response(400, description='Bad response')
def openapi_test_swagger_types(
        request,
        hug_timer,
        swagger_types_number: fields.Integer(),
        swagger_types_in_range_1_5: fields.Integer() = 3) -> TestingSchema():
    """Endpoint with marshmallow types"""
    return {
        'swagger_types_number': swagger_types_number,
        'swagger_types_in_range_1_5': swagger_types_in_range_1_5
    }

@hug.post('/swagger/post-body')  # TODO: check with last slash
@swagger.response(201, description='Created', schema=TestingSchema())
def openapi_post_body(body: TestingSchema()) -> TestingSchema():
    return body

Features:

  • support marshmallow fields as types
  • support marshmallow schemas for body and response
  • use decorator for responses instead of docstring
  • support versioning
  • support external schemas file

Right now it's ugly solution and there is many not implemented features, but it's works =)

nonamenix avatar Nov 12 '17 10:11 nonamenix

Hi @nonamenix and everyone else : Have anyone be using the solution provided by @nonamenix extensively? I need my microservices to publish their swagger specs, but I'm not using Marshmallow types (yet). Before refactoring it all, I'd like to know if it's worth it. If it's not working yet, and that I have to rework my code to work with another framework (even though I'd prefer not to, as I really enjoy Hug), It would probably save me some days not to refactor my code to use Marshmallow as a first testing step. Thanks Brice

briceparent avatar Oct 30 '18 07:10 briceparent

is this solve?

jshwelz avatar Jan 24 '19 18:01 jshwelz

@timothycrosley Like @philipbjorge, I think that working from documentation output is too far from the original object and forces us to implement a custom parser to get to the data.

@philipbjorge @sloria I've worked on implementing a plugin for apispec to support hug handlers a bit and got something working however I met some problems. apispec requires we add paths. We can extend it with a helper to load path and operations from an handler but in hug(with version support) we can have multiple path bound to a single handler. So which one do I choose ?

Also, when it came to documenting operations, I also resorted to using doc string. I wanted to push a little further and try to generate spec for parameters as well. Hug supports annotations so it should be easy. Here are some problems:

  • If a field is of type marshmallow, I can't assume the marshmallow apispec plugin is also configured, I could look for it though but if it's not loaded, I have to skip that field otherwise I'll end up re-doing the marshmallow plugin and it is already great so there is no point to that.
  • hug's types do not support meta data so I can't add specific documentation to them so generated spec would only be parameter name and type. I could work around that by creating new hug types for every parameter but this is counter productive.

As it is, my plugin is also tightly coupled to hug's internals in order to retrieve routes, methods and parameters from handler function so the plugin could break easily.

So I've pretty much came full circle and think it would be better to implement a format in documentation like @prashnts first suggested. It could use apispec under the hood to leverage marshmallow plugin and this way it would be easy to add all routes from a single handler.

@sloria I'd like to be proven wrong with my current plugin implementation and be able to fully support hug in apispec. If you think that binding hug plugin to marshmallow one is ok and we can find a way to add multiple path from a single handler, I think it could work granted it would still be tightly coupled to hug's internal.

What do you all think ?

do you have the plugin working?

jshwelz avatar Jan 25 '19 01:01 jshwelz

Just some extra input:

If the final solution ends up being plugin-based, I imagine the best way to go might be with APISpec, Marshmallow and maybe Webargs.

If it's integrated into Hug, it might make sense to check Pydantic too, as it is based on standard Python type hints and it has support for JSON Schema, which is used by OpenAPI.

That's what I'm using in https://github.com/tiangolo/fastapi, it might be useful as an example/reference/alternative implementation.

tiangolo avatar Jan 25 '19 19:01 tiangolo

Any updates on this at all? Thanks for all the hard work!

samhardyhey avatar Feb 08 '19 04:02 samhardyhey

Checking in :)

pawarren avatar Mar 12 '19 01:03 pawarren

This would be a great feature. I'm probably right now too new to hug to help much, but I am learning :)

danballance avatar Apr 07 '19 07:04 danballance