<commit_before>from functools import wraps
from django.contrib.auth import get_user_model
from django.db.models import Model, Q
from django.db.transaction import atomic
from yunity.api.ids import ids_uri_pattern_delim
from yunity.models import Conversation as ConversationModel
from yunity.resources.http.status import HTTP_400_BAD_REQUEST
from yunity.utils.request import JsonRequest
from yunity.utils.validation import ValidationFailure


def json_request(func):
    """Decorator to validate that the body of a request is in JSON
    (gives a 400 response if the request body does not decode into valid json).

    The decorator modifies the request object, parsing the request.body field into a dictionary.

    Note: This decorator should only be used to decorate http-dispatch instance methods on subclasses of ApiBase.
    """

    @wraps(func)
    def wrapper(api_base, request, *args, **kwargs):
        try:
            request = JsonRequest.from_http_request(request)
        except ValueError:
            return api_base.validation_failure(reason='not a valid json request')

        return func(api_base, request, *args, **kwargs)
    return wrapper


def request_parameter(with_name, of_type=str, optional=False):
    """Decorator to validate that the named parameter on the request body exists and passes some validation
    (gives a 400 response if any of the resources do not convert to the type).

    Note: This decorator should only be used on http-dispatch methods on ApiBase.

    :type with_name: str
    :type of_type: function :: str -> T
    :type optional: bool
    """

    def decorator(func):
        @wraps(func)
        def wrapper(api_base, request, *args, **kwargs):
            try:
                request.body[with_name] = of_type(request.body[with_name])
            except KeyError:
                if not optional:
                    return api_base.validation_failure(reason='missing request parameter {}'.format(with_name))
            except ValidationFailure as e:
                return api_base.validation_failure(reason=e.message, status=e.status)

            return func(api_base, request, *args, **kwargs)
        return wrapper
    return decorator


def uri_resource(with_name, of_type=str, with_multi_resource_separator=ids_uri_pattern_delim):
    """Decorator to parse one or more resources from an URI and convert them to a certain type
    (gives a 400 response if any of the resources do not convert to the type).

    If the type is a model, look-up the intances of the model by ids of the resources
    (gives a 404 response if any of the resources are not found).

    Note: This decorator should only be used on http-dispatch methods on ApiBase.

    :type with_name: str
    :type of_type: function :: str -> T
    :type with_multi_resource_separator: str
    """
    def decorator(func):
        @wraps(func)
        def wrapper(api_base, *args, **kwargs):
            raw_params = kwargs.get(with_name, '').split(with_multi_resource_separator)
            if raw_params:
                if type(of_type) == type(Model):
                    parsed_params = of_type.objects.filter(id__in=raw_params)
                    if len(parsed_params) != len(raw_params):
                        return api_base.not_found(reason='one or more {}s do not exist'.format(of_type.__class__.__name__))
                else:
                    try:
                        parsed_params = [of_type(raw_param) for raw_param in raw_params]
                    except ValueError:
                        return api_base.validation_failure(reason='one or more parameters does not have type {}'.format(of_type.__name__))
                kwargs[with_name] = parsed_params if len(parsed_params) > 1 else parsed_params[0]
<commit_msg>Add min/max limit when parsing uri parameter<commit_after>from functools import wraps
from django.contrib.auth import get_user_model
from django.db.models import Model, Q
from django.db.transaction import atomic
from yunity.api.ids import ids_uri_pattern_delim
from yunity.models import Conversation as ConversationModel
from yunity.resources.http.status import HTTP_400_BAD_REQUEST
from yunity.utils.request import JsonRequest
from yunity.utils.validation import ValidationFailure


def json_request(func):
    """Decorator to validate that the body of a request is in JSON
    (gives a 400 response if the request body does not decode into valid json).

    The decorator modifies the request object, parsing the request.body field into a dictionary.

    Note: This decorator should only be used to decorate http-dispatch instance methods on subclasses of ApiBase.
    """

    @wraps(func)
    def wrapper(api_base, request, *args, **kwargs):
        try:
            request = JsonRequest.from_http_request(request)
        except ValueError:
            return api_base.validation_failure(reason='not a valid json request')

        return func(api_base, request, *args, **kwargs)
    return wrapper


def request_parameter(with_name, of_type=str, optional=False):
    """Decorator to validate that the named parameter on the request body exists and passes some validation
    (gives a 400 response if any of the resources do not convert to the type).

    Note: This decorator should only be used on http-dispatch methods on ApiBase.

    :type with_name: str
    :type of_type: function :: str -> T
    :type optional: bool
    """

    def decorator(func):
        @wraps(func)
        def wrapper(api_base, request, *args, **kwargs):
            try:
                request.body[with_name] = of_type(request.body[with_name])
            except KeyError:
                if not optional:
                    return api_base.validation_failure(reason='missing request parameter {}'.format(with_name))
            except ValidationFailure as e:
                return api_base.validation_failure(reason=e.message, status=e.status)

            return func(api_base, request, *args, **kwargs)
        return wrapper
    return decorator


def uri_resource(with_name, of_type=str, with_multi_resource_separator=ids_uri_pattern_delim, min_resources=1, max_resources=None):
    """Decorator to parse one or more resources from an URI and convert them to a certain type
    (gives a 400 response if any of the resources do not convert to the type).

    If the type is a model, look-up the instances of the model by ids of the resources
    (gives a 404 response if any of the resources are not found).

    Returns a list of results except when exactly one result is expected, that is returned.

    Note: This decorator should only be used on http-dispatch methods on ApiBase.

    :type with_name: str
    :type of_type: function :: str -> T
    :type with_multi_resource_separator: str
    """
    def decorator(func):
        @wraps(func)
        def wrapper(api_base, *args, **kwargs):
            raw_params = kwargs.get(with_name, '').split(with_multi_resource_separator)
            if raw_params:
                if len(raw_params) < min_resources:
                    return api_base.validation_failure(reason='parameter count has to be at least {}'.format(min_resources))
                elif max_resources is not None and len(raw_params) > max_resources:
                    return api_base.validation_failure(reason='parameter count has to be at most {}'.format(max_resources))
                if type(of_type) == type(Model):
                    parsed_params = of_type.objects.filter(id__in=raw_params)
                    if len(parsed_params) != len(raw_params):
                        return api_base.not_found(reason='one or more {}s do not exist'.format(of_type.__class__.__name__))
                else:
                    try:
                        parsed_params = [of_type(raw_param) for raw_param in raw_params]
                    except ValueError:
                        return api_base.validation_failure(reason='one or more parameters does not have type {}'.format(of_type.__name__))
                kwargs[with_name] = parsed_params \
                                        if max_resources is None or max_resources > 1 or min_resources != 1 \
                                    else parsed_params[0]

{'fraction_non_alphanumeric': 0.0714546561168594, 'fraction_numerical': 0.004382227632379793, 'mean_word_length': 2.765352887259395, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '947298', 'n_tokens_mistral': 2233, 'n_tokens_neox': 2145, 'n_words': 775}