<commit_before>"""
API Containers
~~~~~~~~~~~~~~

Containers that provide structure to an API.

"""
from __future__ import absolute_import

import collections
import logging

from odin.codecs import json_codec
from odin.exceptions import ValidationError
from odin.utils import getmeta

# Imports for typing support
from typing import Union, Tuple, Any, Generator, Dict, Type, Optional  # noqa
from odin import Resource  # noqa
from .data_structures import BaseHttpRequest  # noqa

from . import _compat
from . import content_type_resolvers
from .constants import Method, HTTPStatus
from .data_structures import UrlPath, NoPath, HttpResponse, MiddlewareList
from .decorators import Operation, Tags
from .exceptions import ImmediateHttpResponse
from .helpers import resolve_content_type, create_response
from .resources import Error


logger = logging.getLogger(__name__)

CODECS = {json_codec.CONTENT_TYPE: json_codec}

# Attempt to load other codecs that have dependencies
try:
    from odin.codecs import msgpack_codec
    CODECS[msgpack_codec.CONTENT_TYPE] = msgpack_codec
except ImportError:
    pass

try:
    from odin.codecs import yaml_codec
    CODECS[yaml_codec.CONTENT_TYPE] = yaml_codec
except ImportError:
    pass


class ResourceApiMeta(type):
    """
    Meta class that resolves endpoints to routes.
    """
    def __new__(mcs, name, bases, attrs):
        super_new = super(ResourceApiMeta, mcs).__new__

        # attrs will never be empty for classes declared in the standard way
        # (ie. with the `class` keyword). This is quite robust.
        if name == 'NewBase' and attrs == {}:
            return super_new(mcs, name, bases, attrs)

        parents = [
            b for b in bases
            if isinstance(b, ResourceApiMeta) and not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))
        ]
        if not parents:
            # If this isn't a subclass of don't do anything special.
            return super_new(mcs, name, bases, attrs)

        # Determine the resource used by this API (handle inherited resources)
        api_resource = attrs.get('resource')
        if api_resource is None:
            for base in bases:
                api_resource = base.resource
                if api_resource:
                    break

        # Get operations
        operations = []
        for obj in attrs.values():
            if isinstance(obj, Operation):
                operations.append(obj)

        # Get routes from parent objects
        for parent in parents:
            parent_ops = getattr(parent, '_operations', None)
            if parent_ops:
                operations.extend(parent_ops)

        new_class = super_new(mcs, name, bases, attrs)
        setattr(new_class, '_operations', sorted(operations, key=lambda o: o.sort_key))

        return new_class


class ResourceApi(_compat.with_metaclass(ResourceApiMeta)):
    """
    Base framework specific ResourceAPI implementations.
    """
    api_name = None  # type: str
    """
    Name of the API endpoint
    """

    resource = None
    """
    The resource this API is modelled on.
    """

    path_prefix = UrlPath()
    """
    Prefix to prepend to any generated path.
    """

    tags = None
    """
    Resource API tags
    """

    parent = None

    def __init__(self):
        if not self.api_name:
            self.api_name = getmeta(self.resource).name.lower()

        # Append APIs name to path prefix
        self.path_prefix += self.api_name

        for operation in self._operations:
            operation.bind_to_instance(self)

    def op_paths(self, path_base):
        # type: (Union[str, UrlPath]) -> Generator[Tuple[UrlPath, Operation]]
        """
        Return all operations stored in containers.
        """
        path_base += self.path_prefix

        for operation in self._operations:
            for op_path in operation.op_paths(path_base):
                yield op_path


class ApiContainer(object):
    """
    Container or API endpoints.
    
    This class is intended as a base class for other API classes. Containers
    can also be nested. This is used to support versions etc.
    
    """
    def __init__(self, *containers, **options):
        # type: (*Union[Operation, ApiContainer, ResourceApi], **Any) -> None
        self.containers = list(containers)

        # Set self as the parent
        for container in self.containers:
            container.parent = self

        # Having options at the end is work around until support for
        # Python < 3.5 is dropped, at that point keyword only args will
        # be used in place of the options kwargs. eg:
        #   (*containers:Union[Operation, ApiContainer], name:str=None, path_prefix:Union[str, UrlPath]=None) -> None
        self.name = name = options.pop('name', None)
        path_prefix = options.pop('path_prefix', None)
        if path_prefix:
            self.path_prefix = UrlPath.from_object(path_prefix)
        elif name:
            self.path_prefix = UrlPath.parse(name)
        else:
            self.path_prefix = NoPath

        if options:
            raise TypeError("Got an unexpected keyword argument(s) {}", options.keys())

    def operation(self, path, methods=Method.GET, resource=None, tags=None):
        # type: (UrlPath, Union(Method, Tuple[Method]), Type[Resource], Tags) -> Operation
        """
        :param path: A sub path that can be used as a action.
        :param methods: HTTP method(s) this function responses to.
        :param resource: Specify the resource that this function encodes/decodes,
            default is the one specified on the ResourceAPI instance.
        :param tags: Tags to be applied to operation

        """
        def inner(callback):
            operation = Operation(callback, path, methods, resource, tags)
            self.containers.append(operation)
            return operation
        return inner

    def op_paths(self, path_base=None):
        # type: (Union[str, UrlPath]) -> Generator[Tuple[UrlPath, Operation]]
        """
        Return all operations stored in containers.
        """
        if path_base:
            path_base += self.path_prefix<commit_msg>Extend ApiContainer.operation to accept summary and middleware arguments<commit_after>"""
API Containers
~~~~~~~~~~~~~~

Containers that provide structure to an API.

"""
from __future__ import absolute_import

import collections
import logging

from odin.codecs import json_codec
from odin.exceptions import ValidationError
from odin.utils import getmeta

# Imports for typing support
from typing import Union, Tuple, Any, Generator, Dict, Type, Optional  # noqa
from odin import Resource  # noqa
from .data_structures import BaseHttpRequest  # noqa

from . import _compat
from . import content_type_resolvers
from .constants import Method, HTTPStatus
from .data_structures import UrlPath, NoPath, HttpResponse, MiddlewareList
from .decorators import Operation, Tags
from .exceptions import ImmediateHttpResponse
from .helpers import resolve_content_type, create_response
from .resources import Error


logger = logging.getLogger(__name__)

CODECS = {json_codec.CONTENT_TYPE: json_codec}

# Attempt to load other codecs that have dependencies
try:
    from odin.codecs import msgpack_codec
    CODECS[msgpack_codec.CONTENT_TYPE] = msgpack_codec
except ImportError:
    pass

try:
    from odin.codecs import yaml_codec
    CODECS[yaml_codec.CONTENT_TYPE] = yaml_codec
except ImportError:
    pass


class ResourceApiMeta(type):
    """
    Meta class that resolves endpoints to routes.
    """
    def __new__(mcs, name, bases, attrs):
        super_new = super(ResourceApiMeta, mcs).__new__

        # attrs will never be empty for classes declared in the standard way
        # (ie. with the `class` keyword). This is quite robust.
        if name == 'NewBase' and attrs == {}:
            return super_new(mcs, name, bases, attrs)

        parents = [
            b for b in bases
            if isinstance(b, ResourceApiMeta) and not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))
        ]
        if not parents:
            # If this isn't a subclass of don't do anything special.
            return super_new(mcs, name, bases, attrs)

        # Determine the resource used by this API (handle inherited resources)
        api_resource = attrs.get('resource')
        if api_resource is None:
            for base in bases:
                api_resource = base.resource
                if api_resource:
                    break

        # Get operations
        operations = []
        for obj in attrs.values():
            if isinstance(obj, Operation):
                operations.append(obj)

        # Get routes from parent objects
        for parent in parents:
            parent_ops = getattr(parent, '_operations', None)
            if parent_ops:
                operations.extend(parent_ops)

        new_class = super_new(mcs, name, bases, attrs)
        setattr(new_class, '_operations', sorted(operations, key=lambda o: o.sort_key))

        return new_class


class ResourceApi(_compat.with_metaclass(ResourceApiMeta)):
    """
    Base framework specific ResourceAPI implementations.
    """
    api_name = None  # type: str
    """
    Name of the API endpoint
    """

    resource = None
    """
    The resource this API is modelled on.
    """

    path_prefix = UrlPath()
    """
    Prefix to prepend to any generated path.
    """

    tags = None
    """
    Resource API tags
    """

    parent = None

    def __init__(self):
        if not self.api_name:
            self.api_name = getmeta(self.resource).name.lower()

        # Append APIs name to path prefix
        self.path_prefix += self.api_name

        for operation in self._operations:
            operation.bind_to_instance(self)

    def op_paths(self, path_base):
        # type: (Union[str, UrlPath]) -> Generator[Tuple[UrlPath, Operation]]
        """
        Return all operations stored in containers.
        """
        path_base += self.path_prefix

        for operation in self._operations:
            for op_path in operation.op_paths(path_base):
                yield op_path


class ApiContainer(object):
    """
    Container or API endpoints.
    
    This class is intended as a base class for other API classes. Containers
    can also be nested. This is used to support versions etc.
    
    """
    def __init__(self, *containers, **options):
        # type: (*Union[Operation, ApiContainer, ResourceApi], **Any) -> None
        self.containers = list(containers)

        # Set self as the parent
        for container in self.containers:
            container.parent = self

        # Having options at the end is work around until support for
        # Python < 3.5 is dropped, at that point keyword only args will
        # be used in place of the options kwargs. eg:
        #   (*containers:Union[Operation, ApiContainer], name:str=None, path_prefix:Union[str, UrlPath]=None) -> None
        self.name = name = options.pop('name', None)
        path_prefix = options.pop('path_prefix', None)
        if path_prefix:
            self.path_prefix = UrlPath.from_object(path_prefix)
        elif name:
            self.path_prefix = UrlPath.parse(name)
        else:
            self.path_prefix = NoPath

        if options:
            raise TypeError("Got an unexpected keyword argument(s) {}", options.keys())

    def operation(self, path, methods=Method.GET, resource=None, tags=None, summary=None, middleware=None):
        # type: (UrlPath, Union(Method, Tuple[Method]), Type[Resource], Tags, str, list) -> Operation
        """
        :param path: A sub path that can be used as a action.
        :param methods: HTTP method(s) this function responses to.
        :param resource: Specify the resource that this function encodes/decodes,
            default is the one specified on the ResourceAPI instance.
        :param tags: Tags to be applied to operation
        :param summary: Summary of the what method does (for documentation)
        :param middleware: List of additional middleware

        """
        def inner(callback):
            operation = Operation(callback, path, methods, resource, tags, summary, middleware)
            self.containers.append(operation)
            return operation
        return inner

    def op_paths(self, path_base=None):
        # type: (Union[str, UrlPath]) -> Generator[Tuple[UrlPath, Operation]]
        """
        Return all operations stored in containers.
        """
        if path_base:
            path_base += self.path_prefix
{'fraction_non_alphanumeric': 0.08402761247322066, 'fraction_numerical': 0.0003173847496627787, 'mean_word_length': 2.5255944055944055, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 13, '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': '5071689', 'n_tokens_mistral': 3443, 'n_tokens_neox': 3369, 'n_words': 1160}