axnsan12 / drf-yasg

Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
https://drf-yasg.readthedocs.io/en/stable/
Other
3.42k stars 439 forks source link

Override the `swagger_schema_fields` behavior #291

Open Amoki opened 5 years ago

Amoki commented 5 years ago

I'd like to override the swagger_schema_fields behavior: Instead of totally replacing schema fields with provided fields, I'd like to override just some properties (a deep merge).

Is there a way to subclass FieldInspector.add_manual_fields method without having to subclass/override all inspectors?

def recursive_merge(schema, override_schema):
    for attr, val in override_schema.items():
        schema_val = schema.get(attr, None)
        if isinstance(schema_val, SchemaRef):
            # Can't merge SchemaRef with dict, replace the whole object
            schema[attr] = val
        elif isinstance(val, dict):
            if schema_val:
                recursive_merge(schema_val, val)
            else:
                schema[attr] = val
        else:
            schema[attr] = val

def add_manual_fields(self, serializer_or_field, schema):
    meta = getattr(serializer_or_field, "Meta", None)
    swagger_schema_fields = getattr(meta, "swagger_schema_fields", {})
    if swagger_schema_fields:
        recursive_merge(schema, swagger_schema_fields)

CamelCaseJSONFilter.add_manual_fields = add_manual_fields
RecursiveFieldInspector.add_manual_fields = add_manual_fields
ReferencingSerializerInspector.add_manual_fields = add_manual_fields
ChoiceFieldInspector.add_manual_fields = add_manual_fields
FileFieldInspector.add_manual_fields = add_manual_fields
DictFieldInspector.add_manual_fields = add_manual_fields
HiddenFieldInspector.add_manual_fields = add_manual_fields
RelatedFieldInspector.add_manual_fields = add_manual_fields
SerializerMethodFieldInspector.add_manual_fields = add_manual_fields
SimpleFieldInspector.add_manual_fields = add_manual_fields
StringDefaultFieldInspector.add_manual_fields = add_manual_fields

I share here my merge method even it is not tested and designed only to fit my needs. It may interest someone!

axnsan12 commented 5 years ago

I think this is an inherent problem with the current API design. What you did looks like the only way to achieve it...

Maybe a 2.0 release would make this cleaner, somehow.

Maxyme commented 5 years ago

This is a snippet I wrote for examples, which only overrides the attribute if provided in the swagger_schema_fields property as a example key:

    class Meta:
        model = Model
        fields = ('a', 'b', 'c')
        swagger_schema_fields = {
            'example': {
                'a':'foo'
        }
    def add_manual_fields(self, serializer_or_field, schema):
        meta = getattr(serializer_or_field, 'Meta', None)
        swagger_schema_fields = getattr(meta, 'swagger_schema_fields', {})
        if swagger_schema_fields:
            for attr, val in swagger_schema_fields.items():
                if attr == 'example':
                    for key in val.keys():
                        if key in schema['properties']:
                            schema['properties'][key]['example'] = val[key]
                else:
                    setattr(schema, attr, val)

This would show 'a' as being 'foo', but still leave b and c as their default.

IOR88 commented 5 years ago

Hi guys,

Is there any open change pending to implement recursive merge for add_manual_fields I think it would be very useful as it is the only way to manipulate properties schema through serializer and not creating custom schema from scratch. Maybe some kwarg indicating recursive behavior would be a good option. If I would write a patch could it be accepted ? I mean recursive behavior is sth You would like to implement or You plan to make it easier to change FieldInspector behavior globally ?

Best, Igor

IOR88 commented 5 years ago

@Amoki I have override the add_manual_fields for FieldInspector only and it affected other Inspectors too.

def add_manual_fields(self, serializer_or_field, schema):
    meta = getattr(serializer_or_field, 'Meta', None)
    swagger_schema_fields = getattr(meta, 'swagger_schema_fields', {})
    if swagger_schema_fields:
        for attr, val in swagger_schema_fields.items():
            if isinstance(val, dict) and isinstance(getattr(schema, attr, None), dict):
                to_update = dict(list(getattr(schema, attr).items()) + list(val.items()))
                setattr(schema, attr, to_update)
            else:
                setattr(schema, attr, val)

from drf_yasg.inspectors.field import FieldInspector, SerializerInspector

FieldInspector.add_manual_fields = add_manual_fields
# SerializerInspector.add_manual_fields = add_manual_fields

and serializer:

class TestResponseSerializer(serializers.Serializer):
    x = serializers.IntegerField()

    class Meta:
        swagger_schema_fields = {
            'properties': {
                'x': {'type': 'integer', 'format': None, 'x-attribute': 'example'}
            }
        }
IOR88 commented 5 years ago

Btw one additional question, if we are considering django/django-rest application, where it would be best to place add_manual_fields overriding. Maybe sb already did it ? I would appreciate some advice.

IOR88 commented 5 years ago

Ok at the end I have decided to create a separate python file with method overriding logic and I am importing it in django settings.py.

chgad commented 4 years ago

Btw one additional question, if we are considering django/django-rest application, where it would be best to place add_manual_fields overriding. Maybe sb already did it ? I would appreciate some advice.

I also stumbled upon the issue of manually annotationg fields and could think of a more or less practicable place to make this override efficent. My suggestion would be adding an optional inspector_classes parameter to the get_schema_view() function and passing it trough to the point (sorry, didn't dig deep enough to be more specific) where the Serializers/Fields get actually inspected.

That way one could override the add_manual_fields() method by simply subclassing the Inspectorclass of desire and pass it to the schema creation.

How do you think about that? (I'd be willing to dig deeper an eventually provide a PR ;) )

chgad commented 4 years ago

Looking further into this, isn't your proposal just an extension to the FieldInspector class's add_manual_field() method ?