<commit_before>from collections import namedtuple
import json
import mimeparse
from urllib import urlencode

from django.conf.urls import patterns, include, url
from django.core import paginator
from django.core.serializers.json import DjangoJSONEncoder
from django.core.urlresolvers import NoReverseMatch, reverse
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.cache import patch_vary_headers
from django.views import generic
from django.views.decorators.csrf import csrf_exempt


Objects = namedtuple('Objects', 'queryset page set single')


class API(object):
    """
    This is the main API object. It does not do much except give an overview over
    all resources. It will hold the necessary bits to have more than one API
    with the same models or resources at the same time (f.e. versions).

    Usage::

        api_v1 = API('v1')

        api_v1.register(
            Customer,
            view_init={
                'queryset': Customer.objects.filter(is_active=True),
                'paginate_by': 10,
                })

        api_v1.register(
            Product,
            view_init={
                'queryset': Product.objects.filter(is_active=True)
                'paginate_by': 10,
                })

        api_v1.register(
            Product,
            canonical=False,
            prefix=r'^library/',
            view_class=LibraryResource,
            view_init={
                'queryset': Product.objects.filter(is_active=True),
                'paginate_by': 10,
                })

        urlpatterns = patterns('',
            url(r'^v1/', include(api_v1.urls)),
        )
    """

    def __init__(self, name):
        self.name = name
        self.resources = []
        self.serializers = {}

    @property
    def urls(self):
        """
        Inclusion point in your own URLconf

        Pass the return value to ``include()``.
        """
        urlpatterns = [
            url(r'^$', self, name='api_%s' % self.name),
            ]

        for resource in self.resources:
            urlpatterns.append(url(
                resource['prefix'],
                include(resource['urlpatterns']),
                ))

        return patterns('', *urlpatterns)

    def __call__(self, request):
        """
        Main API view, returns a list of all available resources
        """
        response = {
            '__unicode__': self.name,
            '__uri__': reverse('api_%s' % self.name),
            'resources': [],
            }

        for resource in self.resources:
            r = {
                '__unicode__': resource['model'].__name__.lower(),
                '__uri__': u''.join((response['__uri__'], resource['prefix'].strip('^'))),
                }

            response['resources'].append(r)
            if resource['canonical']:
                response[resource['model'].__name__.lower()] = r

        # TODO content negotiation :-(
        return HttpResponse(json.dumps(response), mimetype='application/json')

    def register(self, model, view_class=None, canonical=True,
            decorators=[csrf_exempt], prefix=None, view_init=None,
            serializer=None):
        """
        Registers another resource on this API. The sole required argument is the
        Django model which should be exposed. The other arguments are:

        - ``view_class``: The resource view class used, defaults to
          :class:`towel.api.Resource`.
        - ``canonical``: Whether this resource is the canonical location of the
          model in this API. Allows registering the same model several times in
          the API (only one location should be the canonical location!)
        - ``decorators``: A list of decorators which should be applied to the
          view. Function decorators only, method decorators aren't supported. The
          list is applied in reverse, the order is therefore the same as with the
          ``@`` notation. It's recommended to always pass ``csrf_exempt`` here,
          otherwise API POSTing will have to include a valid CSRF middleware token.
        - ``prefix``: The prefix for this model, defaults to the model name in
          lowercase. You should include a caret and a trailing slash if you specify
          this yourself (``prefix=r'^library/'``).
        - ``view_init``: Python dictionary which contains keyword arguments used
          during the instantiation of the ``view_class``.
        - ``serializer``: Function which takes a model instance, the API instance
          and additional keyword arguments (accept ``**kwargs`` for forward
          compatibility) and returns the serialized representation as a Python dict.
        """

        view_class = view_class or Resource
        view_init = view_init or {}

        if 'model' not in view_init:
            view_init['model'] = view_init.get('queryset').model or model

        view = view_class.as_view(api=self, **view_init)

        name = lambda ident: None
        if canonical:
            opts = model._meta
            name = lambda ident: '_'.join((
                self.name, opts.app_label, opts.module_name, ident))

        if decorators:
            for dec in reversed(decorators):
                view = dec(view)

        self.resources.append({
            'model': model,
            'canonical': canonical,
            'prefix': prefix or r'^%s/' % model.__name__.lower(),
            'urlpatterns': patterns('',
                url(r'^$', view, name=name('list')),
                url(r'^(?P<pk>\d+)/$', view, name=name('detail')),
                url(r'^(?P<pks>(?:\d+;)*\d+);?/$', view, name=name('set')),
                ),
            })

        if serializer:
            self.serializers[model] = serializer

    def serialize_instance(self, instance, **kwargs):
        serializer = self.serializers.get(instance.__class__, serialize_model_instance)
        return serializer(instance, api=self, **kwargs)


def serialize_model_instance(instance, api, inline_depth=0):
    """
    Serializes a single model instance.

    If ``inline_depth`` is a positive number, ``inline_depth`` levels of related
    objects are inlined. The performance implications of this feature might be
    severe!

    This implementation has a few characteristics you should be aware of:

    - Only objects which have a canonical URI inside this particular API are
      serialized; if no such URI exists, this method returns ``None``.
    - Many to many relations are only processed if ``inline_depth`` has a
      positive value. The reason for this design decision is that the database
      has to be queried for showing the URIs of related objects anyway and
      because of that we either show the full objects or nothing at all.
    - Some fields (currently only fields with choices) have a machine readable
      and a prettified value. The prettified values are delivered inside the
      ``__pretty__`` dictionary for your convenience.
    """
    uri = api_reverse(instance, 'detail', api_name=api.name,
        pk=instance.pk, fail_silently=True)

    if uri is None:
        return None

    data = {
        '__uri__': uri,
        '__unicode__': unicode(instance),
        '__pretty__': {},
        }
    opts = instance._meta

    for f in opts.fields:
        if f.rel:
            if inline_depth > 0:
                if getattr(instance, f.name):
                    data[f.name] = api.serialize_instance(
                        getattr(instance, f.name),
                        inline_depth=inline_depth-1,
                        )
                else:
                    data[f.name] = None

            else:
                try:
                    data[f.name] = api_reverse(f.rel.to, 'detail', api_name=api.name,
                        pk=f.value_from_object(instance))
                except NoReverseMatch:
                    continue
<commit_msg>Add **kwargs to serialize_model_instance (but abort if anything is passed)<commit_after>from collections import namedtuple
import json
import mimeparse
from urllib import urlencode

from django.conf.urls import patterns, include, url
from django.core import paginator
from django.core.serializers.json import DjangoJSONEncoder
from django.core.urlresolvers import NoReverseMatch, reverse
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.cache import patch_vary_headers
from django.views import generic
from django.views.decorators.csrf import csrf_exempt


Objects = namedtuple('Objects', 'queryset page set single')


class API(object):
    """
    This is the main API object. It does not do much except give an overview over
    all resources. It will hold the necessary bits to have more than one API
    with the same models or resources at the same time (f.e. versions).

    Usage::

        api_v1 = API('v1')

        api_v1.register(
            Customer,
            view_init={
                'queryset': Customer.objects.filter(is_active=True),
                'paginate_by': 10,
                })

        api_v1.register(
            Product,
            view_init={
                'queryset': Product.objects.filter(is_active=True)
                'paginate_by': 10,
                })

        api_v1.register(
            Product,
            canonical=False,
            prefix=r'^library/',
            view_class=LibraryResource,
            view_init={
                'queryset': Product.objects.filter(is_active=True),
                'paginate_by': 10,
                })

        urlpatterns = patterns('',
            url(r'^v1/', include(api_v1.urls)),
        )
    """

    def __init__(self, name):
        self.name = name
        self.resources = []
        self.serializers = {}

    @property
    def urls(self):
        """
        Inclusion point in your own URLconf

        Pass the return value to ``include()``.
        """
        urlpatterns = [
            url(r'^$', self, name='api_%s' % self.name),
            ]

        for resource in self.resources:
            urlpatterns.append(url(
                resource['prefix'],
                include(resource['urlpatterns']),
                ))

        return patterns('', *urlpatterns)

    def __call__(self, request):
        """
        Main API view, returns a list of all available resources
        """
        response = {
            '__unicode__': self.name,
            '__uri__': reverse('api_%s' % self.name),
            'resources': [],
            }

        for resource in self.resources:
            r = {
                '__unicode__': resource['model'].__name__.lower(),
                '__uri__': u''.join((response['__uri__'], resource['prefix'].strip('^'))),
                }

            response['resources'].append(r)
            if resource['canonical']:
                response[resource['model'].__name__.lower()] = r

        # TODO content negotiation :-(
        return HttpResponse(json.dumps(response), mimetype='application/json')

    def register(self, model, view_class=None, canonical=True,
            decorators=[csrf_exempt], prefix=None, view_init=None,
            serializer=None):
        """
        Registers another resource on this API. The sole required argument is the
        Django model which should be exposed. The other arguments are:

        - ``view_class``: The resource view class used, defaults to
          :class:`towel.api.Resource`.
        - ``canonical``: Whether this resource is the canonical location of the
          model in this API. Allows registering the same model several times in
          the API (only one location should be the canonical location!)
        - ``decorators``: A list of decorators which should be applied to the
          view. Function decorators only, method decorators aren't supported. The
          list is applied in reverse, the order is therefore the same as with the
          ``@`` notation. It's recommended to always pass ``csrf_exempt`` here,
          otherwise API POSTing will have to include a valid CSRF middleware token.
        - ``prefix``: The prefix for this model, defaults to the model name in
          lowercase. You should include a caret and a trailing slash if you specify
          this yourself (``prefix=r'^library/'``).
        - ``view_init``: Python dictionary which contains keyword arguments used
          during the instantiation of the ``view_class``.
        - ``serializer``: Function which takes a model instance, the API instance
          and additional keyword arguments (accept ``**kwargs`` for forward
          compatibility) and returns the serialized representation as a Python dict.
        """

        view_class = view_class or Resource
        view_init = view_init or {}

        if 'model' not in view_init:
            view_init['model'] = view_init.get('queryset').model or model

        view = view_class.as_view(api=self, **view_init)

        name = lambda ident: None
        if canonical:
            opts = model._meta
            name = lambda ident: '_'.join((
                self.name, opts.app_label, opts.module_name, ident))

        if decorators:
            for dec in reversed(decorators):
                view = dec(view)

        self.resources.append({
            'model': model,
            'canonical': canonical,
            'prefix': prefix or r'^%s/' % model.__name__.lower(),
            'urlpatterns': patterns('',
                url(r'^$', view, name=name('list')),
                url(r'^(?P<pk>\d+)/$', view, name=name('detail')),
                url(r'^(?P<pks>(?:\d+;)*\d+);?/$', view, name=name('set')),
                ),
            })

        if serializer:
            self.serializers[model] = serializer

    def serialize_instance(self, instance, **kwargs):
        serializer = self.serializers.get(instance.__class__, serialize_model_instance)
        return serializer(instance, api=self, **kwargs)


def serialize_model_instance(instance, api, inline_depth=0, **kwargs):
    """
    Serializes a single model instance.

    If ``inline_depth`` is a positive number, ``inline_depth`` levels of related
    objects are inlined. The performance implications of this feature might be
    severe!

    This implementation has a few characteristics you should be aware of:

    - Only objects which have a canonical URI inside this particular API are
      serialized; if no such URI exists, this method returns ``None``.
    - Many to many relations are only processed if ``inline_depth`` has a
      positive value. The reason for this design decision is that the database
      has to be queried for showing the URIs of related objects anyway and
      because of that we either show the full objects or nothing at all.
    - Some fields (currently only fields with choices) have a machine readable
      and a prettified value. The prettified values are delivered inside the
      ``__pretty__`` dictionary for your convenience.
    """

    assert not kwargs, 'Unknown keyword arguments to serialize_model_instance'

    uri = api_reverse(instance, 'detail', api_name=api.name,
        pk=instance.pk, fail_silently=True)

    if uri is None:
        return None

    data = {
        '__uri__': uri,
        '__unicode__': unicode(instance),
        '__pretty__': {},
        }
    opts = instance._meta

    for f in opts.fields:
        if f.rel:
            if inline_depth > 0:
                if getattr(instance, f.name):
                    data[f.name] = api.serialize_instance(
                        getattr(instance, f.name),
                        inline_depth=inline_depth-1,
                        )
                else:
                    data[f.name] = None

            else:
                try:
                    data[f.name] = api_reverse(f.rel.to, 'detail', api_name=api.name,
                        pk=f.value_from_object(instance))
                except NoReverseMatch:
                    continue

{'fraction_non_alphanumeric': 0.09704746480627881, 'fraction_numerical': 0.002740749968855114, 'mean_word_length': 2.0940450953941028, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1010843', 'n_tokens_mistral': 4303, 'n_tokens_neox': 4068, 'n_words': 1370}