Skip to content
This repository was archived by the owner on Mar 18, 2019. It is now read-only.
This repository was archived by the owner on Mar 18, 2019. It is now read-only.

CamelCase CoreJSON Codec for easier integration with non-Python clients #162

Open

Description

@Sonictherocketman

I've been integrating the new DRF Schemas into my app and converting it to use the CoreAPI JS client. Overall the process has been super smooth, however there's some inconsistencies in variable casing between my API (which uses djangorestframework_camel_case to render responses in a more JS friendly format) and my schema (which does not support this).

I've spent some time messing with it and I've gotten a subclass of the SchemaJSView to render camelcase using the same renderer as the rest of my app.

While this works for me, I'd love to see support for this added to DRF and CoreAPI for others to more easily use. Please let me know what you think and how I could PR this to CoreAPI and DRF.

My CamelCaseCoreAPI Codec

import json

from coreapi.compat import COMPACT_SEPARATORS, VERBOSE_SEPARATORS
from coreapi.compat import force_bytes, string_types, urlparse
from coreapi.codecs.corejson import (
    CoreJSONCodec, _document_to_primitive, _primitive_to_document
)
from coreapi.document import Document, Link, Array, Object, Error, Field
from djangorestframework_camel_case.util import camelize, underscoreize


class CamelCaseCoreJSONCodec(CoreJSONCodec):
    """ A subclass of the CoreAPI CoreJSON Codec, that additionally performs
    a simple transformation to CamelCase-style syntax for easy consumption
    by Javascript and other similar clients.
    """

    def decode(self, bytestring, **options):
        """
        Takes a bytestring and returns a document.
        """
        base_url = options.get('base_url')

        try:
            data = underscoreize(json.loads(bytestring.decode('utf-8')))
        except ValueError as exc:
            raise ParseError('Malformed JSON. %s' % exc)

        doc = _primitive_to_document(data, base_url)

        if isinstance(doc, Object):
            doc = Document(content=dict(doc))
        elif not (isinstance(doc, Document) or isinstance(doc, Error)):
            raise ParseError('Top level node should be a document or error.')

        return doc

    def encode(self, document, **options):
        """
        Takes a document and returns a bytestring.
        """
        indent = options.get('indent')

        if indent:
            kwargs = {
                'ensure_ascii': False,
                'indent': 4,
                'separators': VERBOSE_SEPARATORS
            }
        else:
            kwargs = {
                'ensure_ascii': False,
                'indent': None,
                'separators': COMPACT_SEPARATORS
            }

        data = _document_to_primitive(document)
        return force_bytes(json.dumps(camelize(data), **kwargs))

Note: In messing around I found that I needed to repeat a lot of the CoreJSONCodec in my code. I'm wondering if we can tweak the CoreJSONCodec a bit to more elegantly do the encoding/decoding.

Edit: wording.

Activity

  1. michaelmarziani commented on Oct 3, 2018

    @michaelmarziani

    @Sonictherocketman I've got the same setup as you and have experienced the same issue. I noted that drf-yasg integrates with drf-camel-case out of the box, but is generating OpenAPI 2.0 documents which CoreAPI can't use.

    Could you provide some info on how you subclassed SchemaJSView to integrate this into your schema generation flow?

  2. Sonictherocketman commented on Oct 5, 2018

    @Sonictherocketman
    Author

    IIRC the SchemaJSView didn't provide an easy subclassing interface to generate the docs/schema automatically. I ended up diving into DRF and just wrapping the view generator function.

    class CamelCaseSchemaJSRenderer(SchemaJSRenderer):
    
        def render(self, data, accepted_media_type=None, renderer_context=None):
            codec = CamelCaseCoreJSONCodec()
            schema = base64.b64encode(codec.encode(data)).decode('ascii')
    
            template = loader.get_template(self.template)
            context = {'schema': mark_safe(schema)}
            request = renderer_context['request']
            return template.render(context, request=request)
    
    
    def get_camelcase_schemajs_view(
            title=None, description=None, schema_url=None, public=True,
            patterns=None, generator_class=SchemaGenerator,
            authentication_classes=settings.DEFAULT_AUTHENTICATION_CLASSES,
            permission_classes=settings.DEFAULT_PERMISSION_CLASSES):
        
        # Manual override
        renderer_classes = [CamelCaseSchemaJSRenderer]
    
        return get_schema_view(
            title=title,
            url=schema_url,
            description=description,
            renderer_classes=renderer_classes,
            public=public,
            patterns=patterns,
            generator_class=generator_class,
            authentication_classes=authentication_classes,
            permission_classes=permission_classes,
        )

    It wasn't terribly elegant but it's gotten the job done for us.

  3. michaelmarziani commented on Oct 8, 2018

    @michaelmarziani

    Thanks for the pointers! I was in and around that code but hadn't been successful at wrapping that generator function.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions