Skip to content

Instantly share code, notes, and snippets.

@oldnote
Last active December 11, 2019 10:34
Show Gist options
  • Save oldnote/0f58012ffb5be5e229e70dd44baa4258 to your computer and use it in GitHub Desktop.
Save oldnote/0f58012ffb5be5e229e70dd44baa4258 to your computer and use it in GitHub Desktop.
import rest_framework.serializers
import graphql.execution.executor
from django.db.models import Model
from graphene.utils import str_converters
from functools import lru_cache
@lru_cache(maxsize=None)
def get_serializer(model_class, model_fields):
"""Create and cache serializer object for given model with given fields."""
class ModelSerializer(rest_framework.serializers.ModelSerializer):
class Meta:
model = model_class
fields = model_fields
return ModelSerializer()
def complete_value(exe_context, return_type, field_asts, info, path, result):
if isinstance(result, Model):
camel_case_fields = exe_context.get_sub_fields(
return_type, field_asts
).keys()
snake_case_fields = tuple(
map(str_converters.to_snake_case, camel_case_fields)
)
serializer = get_serializer(result.__class__, snake_case_fields)
serializer.instance = result
return serializer.data
return _complete_value(
exe_context, return_type, field_asts, info, path, result
)
_complete_value = graphql.execution.executor.complete_value
graphql.execution.executor.complete_value = complete_value
@oldnote
Copy link
Author

oldnote commented Oct 2, 2019

@oldnote
Copy link
Author

oldnote commented Oct 2, 2019

Bugfixes with __typename, id fields support and more speed up by caching:

class DecimalWithFloatReprField(rest_framework.serializers.DecimalField):
    """In graphql decimals represented as floats."""
    def to_representation(self, *a, **kw):
        return float(super().to_representation(*a, **kw))


@lru_cache(maxsize=None)
def get_serializer(model_class, model_fields):
    """Create and cache serializer object for given model with given fields."""

    class ModelSerializer(rest_framework.serializers.ModelSerializer):
        class Meta:
            model = model_class
            fields = model_fields

        serializer_field_mapping = (
            rest_framework.serializers.ModelSerializer.serializer_field_mapping.copy()  # noqa
        )
        serializer_field_mapping[  # noqa
            DecimalField
        ] = DecimalWithFloatReprField

        @lru_cache(maxsize=None)
        def to_representation(self, instance):
            return super().to_representation(instance)

    return ModelSerializer()


def complete_value(exe_context, return_type, field_asts, info, path, result):
    """
    Following functin used to speedup graphql response time.

    XXX: Nested queried objects will be represented as id's, because
    restframework serializer has default depth. For our current usecase
    (candles) we're fine with that.
    """

    # Use default behaviour if not Candle instance.
    if not isinstance(result, games_models.Candle):
        return _complete_value(
            exe_context, return_type, field_asts, info, path, result
        )

    # Get camel cased field names list specified in graphql query.
    camel_case_fields = exe_context.get_sub_fields(
        return_type, field_asts
    ).keys()

    # Transform field names to snake case excluding optional graphql's
    # '__typename' field.
    snake_case_fields = tuple(
        filter(
            lambda field_name: field_name != '__typename',
            map(str_converters.to_snake_case, camel_case_fields),
        )
    )

    # Create or receive cached serializer object.
    serializer = get_serializer(result.__class__, snake_case_fields)
    # Serialize instance to dict.
    serialized_data = serializer.to_representation(result)
    # Transform snake case fields to camel case for graphql.
    serialized_data = funcy.walk_keys(to_camel_case, dict(**serialized_data))

    # Append __typename if was provided.
    if '__typename' in camel_case_fields:
        serialized_data['__typename'] = str(return_type)

    # Transform django's id to relay's if was provided.
    if 'id' in serialized_data:
        serialized_data['id'] = to_global_id(
            str(return_type), serialized_data['id']
        )

    return serialized_data


_complete_value = graphql.execution.executor.complete_value
graphql.execution.executor.complete_value = complete_value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment