django-rest-swagger icon indicating copy to clipboard operation
django-rest-swagger copied to clipboard

No way to document parameters

Open leonardoarroyo opened this issue 7 years ago • 38 comments

New django-rest-swagger versions have deprecated YAML docstrings. There's no documentation demonstrating how to document request fields anymore.

There's some things you can do by changing the filter_backends on ViewSets, for example, but it just doesn't feel right. I still haven't found a way to do this on function based views.

leonardoarroyo avatar Sep 13 '16 04:09 leonardoarroyo

It also seems that coreapi is less descriptive than openapi so the openapi schema will be incomplete since we generate from rest_frameworks coreapi document (via https://github.com/core-api/python-openapi-codec). I need the ability to specify response codes and bodies. IIUC the old way to get around this was with YAML docstrings but those are deprecated.

ameade avatar Sep 15 '16 19:09 ameade

I still do not know how to document parameters. Hope there will be an example in the tutorial app. for example, how to document the parameters(mobile) in the method sendcode?

class UserViewSet(mixins.CreateModelMixin,
                  mixins.RetrieveModelMixin,
                  mixins.UpdateModelMixin,
                  viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsUserOrReadOnly, IsAuthenticated)

    @list_route(methods=['post'])
    def sendcode(self, request):
        """send verify code"""
        from moore.services.yimei import send_sms

        username = request.data.get('mobile')
        verifycode = random.randint(1000, 9999)
        send_sms(username, verifycode)
        return Response('data': 'send ok')

daimon99 avatar Sep 19 '16 11:09 daimon99

I write a custom schemagenerator to deal with parameter:

class SchemaGenerator(schemas.SchemaGenerator):
    def get_link(self, path, method, callback, view):
        """Custom the coreapi using the func.__doc__ .

        if __doc__ of the function exsit, use the __doc__ building the coreapi. else use the default serializer.

        __doc__ in yaml format, eg:

        desc: the desc of this api.
        ret: when success invoked, return xxxx
        err: when error occured, return xxxx
        input:
        - name: mobile
          desc: the mobile number
          type: string
          required: true
          location: form
        - name: promotion
          desc: the activity id
          type: int
          required: true
          location: form
        """
        fields = self.get_path_fields(path, method, callback, view)
        yaml_doc = None
        func = getattr(view, view.action) if getattr(view, 'action', None) else None
        if func and func.__doc__:
            try:
                yaml_doc = yaml.load(func.__doc__)
            except:
                yaml_doc = None
        if yaml_doc and 'desc' in yaml_doc:
            desc = yaml_doc.get('desc', '')
            ret = yaml_doc.get('ret', '')
            err = yaml_doc.get('err', '')
            _method_desc = desc + '<br>' + 'return: ' + ret + '<br>' + 'error: ' + err
            params = yaml_doc.get('input', [])
            for i in params:
                _name = i.get('name')
                _desc = i.get('desc')
                _required = i.get('required', True)
                _type = i.get('type', 'string')
                _location = i.get('location', 'form')
                field = coreapi.Field(
                    name=_name,
                    location=_location,
                    required=_required,
                    description=_desc,
                    type=_type
                )
                fields.append(field)
        else:
            _method_desc = func.__doc__ if func and func.__doc__ else ''
            fields += self.get_serializer_fields(path, method, callback, view)
        fields += self.get_pagination_fields(path, method, callback, view)
        fields += self.get_filter_fields(path, method, callback, view)

        if fields and any([field.location in ('form', 'body') for field in fields]):
            encoding = self.get_encoding(path, method, callback, view)
        else:
            encoding = None

        if self.url and path.startswith('/'):
            path = path[1:]

        return coreapi.Link(
            url=urlparse.urljoin(self.url, path),
            action=method.lower(),
            encoding=encoding,
            fields=fields,
            description=_method_desc
        )

daimon99 avatar Sep 28 '16 11:09 daimon99

@daimon99, on list routes, you can set the filter_backends property on the ViewSet to correctly generate the schema, still it feels a little hacky.

leonardoarroyo avatar Oct 08 '16 07:10 leonardoarroyo

just wait rest framework 3.5. tomchristie said: "Gothca, understand the issue now! Yes, this has been an issue in how we generate the schema for swagger UI. It's resolved as part of the upcoming 3.5 release."

daimon99 avatar Oct 08 '16 08:10 daimon99

https://github.com/tomchristie/django-rest-framework/issues/4241. image

daimon99 avatar Oct 08 '16 08:10 daimon99

So no updates yet regarding this issue??

psychok7 avatar Oct 21 '16 14:10 psychok7

Given that the 3.5 release is out, has this ticket been handled?

kevin-brown avatar Oct 21 '16 15:10 kevin-brown

Maybe mentioning @tomchristie ?

angvp avatar Oct 26 '16 22:10 angvp

There's some level of support for this, but it's a little patchy.

You'd need to look at the implementation of get_link() to see how the fields are built up. The description attribute on fields can then be pulled out by the swagger UI renderer...

  • Serializer fields (request body/form) have the help_text attribute pulled into the description.
  • Filter fields and pagination fields (query params) have whatever get_schema_fields returns (currently I don't think that the various get_schema_fields() implementations populate any field descriptions, tho you could override the get_schema_fields() method using a custom filter/paginator class)
  • URL path fields have no way of setting a description.

tomchristie avatar Oct 26 '16 23:10 tomchristie

Does CoreAPI even support custom type definition? I know you can specify anything in the field.type.

I think you would need to change rest_framework_swagger.renderers.OpenAPICodec so that the returned Swagger object would be extended with definitions:.

Those definitions could be based on Serializers and pushed to coreapi.document.Document with SchemaGenerator maybe. The coreapi.document.Document seem to be accepting any data.

class Document(itypes.Dict):
    # ...
    self._data = {key: _to_immutable(value) for key, value in content.items()}

slykar avatar Nov 27 '16 21:11 slykar

@slykar - I think you're mixing up different concerns in that comment. (What datatypes can be included in a Document, vs. how do we describe parameter help text and parameter types.

I'm currently working on a documentation generator to tie in with Core API, which is helping tease out some of the docs related requirements. I'd suggest we hold further discussion until we can use that as a starting point.

Aside: Also worth re-iterating that I want to see any spec changes to Core API be driven by very explicit tooling requirements. Eg. "Adding X to Core API will allow me to do Y".

tomchristie avatar Nov 28 '16 09:11 tomchristie

@tomchristie - To give some background. What I was trying to achieve was a documentation of profile parameter on my endpoint, which is an embedded document with it's own schema. SchemaGenerator specifies the type as 'object', where it should be a type on it's own, like 'UserProfile'.

When using OpenAPI, you can document such field by setting custom type. In OpenAPI this type is defined by definitions section. This is something you can not do with CoreAPI as far as I know, even tough the CoreAPI Field.type takes arbitrary string and you could put 'UserProfile' there.

The spec mismatch makes it hard to properly document endpoint parameters, utilizing OpenAPI type definitions. This is something I require, otherwise the generated OpenAPI spec is worthless for me.

I was even trying to figure out how to push this type information through SchemaGenerator to CoreAPI to OpenAPI.

Right now I'm back to writing the OpenAPI spec file by hand.

slykar avatar Nov 28 '16 11:11 slykar

Gotcha. And yes, we may well move towards allowing for annotation information about more complex types. Out of interest what tooling are you using to document the API - Swagger UI, or something else?

tomchristie avatar Nov 28 '16 11:11 tomchristie

@tomchristie I'm using Swagger UI, tough, when writing spec by hand I write it using API Blueprint (IMO easier to write and read than OpenAPI), then convert it to OpenAPI spec for Swagger UI.

slykar avatar Nov 28 '16 11:11 slykar

Hey guys any update on this issue?

nikparmar avatar Feb 02 '17 03:02 nikparmar

Yeah upcoming version of REST framework is working towards improving this.

tomchristie avatar Feb 02 '17 08:02 tomchristie

look forward to the new version of REST framework!

for the time being i'm using this class decorator: https://github.com/ministryofjustice/moj-product-dashboard/blob/develop/dashboard/libs/swagger_tools.py#L5

use case: https://github.com/ministryofjustice/moj-product-dashboard/blob/develop/dashboard/apps/dashboard/views.py#L117

cliffxuan avatar Feb 02 '17 15:02 cliffxuan

@cliffxuan Interesting solution. @tomchristie What are your plans for the next version? Would you consider extending the override methods like get_path_fields and get_filter_fields currently on the schema generator onto APIView?

marcgibbons avatar Feb 08 '17 13:02 marcgibbons

@cliffxuan https://github.com/marcgibbons/django-rest-swagger/issues/549#issuecomment-276993383 Your solution works fine for list action. what if we want to customize that at action level, i.e i want a specific query param for retrieve method or update or create method for that matter. How are we going to do that. can we modify your decorator to support at method level?

gauravvjn avatar Mar 06 '17 12:03 gauravvjn

@gjain0 my decorator works only for list view. i don't have a use case for yours yet. feel free to modify it.

cliffxuan avatar Mar 06 '17 13:03 cliffxuan

Has this issue been addressed in any way over the past 8 months? It appears that currently there is no concise readable way to specify parameters for API endpoints, which effectively renders Swagger as unusable.

FilmCoder avatar Mar 07 '17 16:03 FilmCoder

@FilmCoder I'm using @daimon99 solution. modified for my use case. It's working perfect.

here is the complete code https://gist.github.com/gjain0/f536d3988eb61e693ea306305c441bb7

gauravvjn avatar Mar 08 '17 05:03 gauravvjn

It really is mind boggling that this issue remains open after all this time. I should also note that all the presented solutions above are not applicable to function based views

JorisBenschop avatar Apr 01 '17 10:04 JorisBenschop

Using help_text on Serializer works great for parameters - but I'm still missing a way to add a description to the Path parameters.

Would be great if that could be implemented!

frennkie avatar Apr 21 '17 12:04 frennkie

If all you care about is showing parameter types, is required or optional, and help_text, the "Model" view on the swagger page is an easy alternative, as described here (edit: just part 1, rest of article is out of date): http://idratherbewriting.com/2015/12/10/ten-realizations-using-swagger-and-swagger-ui/

Pros: Can be implemented with just Serializers and isn't a hack. Cons: Bad at displaying request data's nested objects and lists.

bwmello avatar Jun 23 '17 20:06 bwmello

@bwmello , this is about documenting input parameters and (more importantly) the ability to enter parameter content within swagger. Your page suggests using markdown, but this ability was removed when the software went to coreAPI, without any viable replacement. This thread is not related to documenting the output parameters, which is what your page also describes.

JorisBenschop avatar Jun 26 '17 11:06 JorisBenschop

Hi, Any update on this issue ?

fijemax avatar Aug 20 '17 20:08 fijemax

Bizarre that a documentation tool for an API, doesn't- wait for it- allow you to document parameters. Kinda like a car that doesn't drive, or a plane that doesn't fly. Can I have the past hour of googling in disbelief back?

Here's my workaround, param_schema.py --

from rest_framework.compat import coreapi, coreschema
from rest_framework.filters import BaseFilterBackend


class ParamSchemaFilter(BaseFilterBackend):
    def get_schema_fields(self, view):
        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
        fields = super().get_schema_fields(view)
        if hasattr(view, 'get_param_fields'):
            print(view)
            fields += view.get_param_fields(view)
        return fields

And in your settings --

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': (
        ...
        'param_schema.ParamSchemaFilter', 
    ),

And in your view/viewset --

class MyAwesomeView(viewsets.GenericViewSet):

    def get_param_fields(self, view):
        fields = [
            coreapi.Field(
                name='my_field',
                required=True,
                location='query',
                schema=coreschema.String(
                    title='My awesome field',
                    description='This is my really awesome field right here, so awesome'
                )
            ),
        ]
        return fields

litchfield avatar Aug 24 '17 05:08 litchfield

Seems like things are moving forward: https://github.com/encode/django-rest-framework/issues/4502

fazpu avatar Sep 08 '17 09:09 fazpu