<commit_before>"""
Copyright 2017 salexkidd

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from django.utils.translation import ugettext as _
from django.core.exceptions import ValidationError
from django.utils.translation import get_language

from rest_framework import serializers as rf_serializers

import simplejson
import codecs
import pprint
import pydoc
import types
import yaml
import warnings
import copy

NOT_AVAILABLE_FIELDS = (
    rf_serializers.ListField,
    rf_serializers.DictField,
    rf_serializers.SerializerMethodField,
)


__all__ = (
    "build_serializer",
    "build_serializer_by_json",
    "build_serializer_by_json_file",
    "build_serializer_by_yaml",
    "build_serializer_by_yaml_file",
)


class DefinableSerializerMeta(rf_serializers.SerializerMetaclass):
    # https://stackoverflow.com/questions/27258557/metaclass-arguments-for-python-3-x

    @classmethod
    def _parse_validate_method(metacls, method_str):
        global_var, local_var = dict(), dict()

        try:
            exec(method_str, global_var, local_var)
            validate_method = local_var["validate_method"]

        except Exception as e:
            raise ValidationError(e)

        if not isinstance(validate_method, types.FunctionType):
            raise ValidationError("Not a function")

        return validate_method

    @classmethod
    def _get_field_class(metacls, field_class_str, serializer_classes):

        # get from Django Restframework serializers
        field_class = getattr(rf_serializers, field_class_str, None)

        # get from builded depending serializers
        if not field_class:
            field_class = serializer_classes.get(field_class_str, None)

        # You can get "<< package>>.<< module >>.<< class >>" string
        if not field_class:
            field_class = pydoc.locate(field_class_str)

        return field_class

    @classmethod
    def _build_fields(metacls, fields_defn, serializer_classes, allow_validate_method):
        fields = dict()
        validate_methods = dict()

        def _build_validators(defn):
            field_name = defn["name"]
            validators = list()

            for validator_defn in defn.get("validators", list()):
                validator_class_path = validator_defn["validator"]
                validator_class = pydoc.locate(validator_class_path)

                if not validator_class:
                    raise ValidationError({
                        field_name: "cannot import name '{}'".format(
                            validator_class_path
                        )
                    })

                try:
                    validators.append(
                        validator_class(
                            *validator_defn.get("args", list()),
                            **validator_defn.get("kwargs", dict())
                        )
                    )
                except Exception as e:
                    raise ValidationError({field_name: e})

            return validators

        def _translation_default(field_name, field_class, field_args, field_kwargs):
            for target in ("label", "help_text",):
                value = field_kwargs.get(target, None)
                if isinstance(value, dict):
                    value = value.get("default", None)
                    if value is None:
                        error_msg = "'default' not in '{}'".format(target)
                        raise ValidationError({field_name: error_msg})

                    field_kwargs[target] = value

            # Choices
            is_choices = all([
                issubclass(field_class, rf_serializers.ChoiceField),
                not issubclass(field_class, rf_serializers.FilePathField)
            ])

            if is_choices:
                new_choices = list()
                for choice in field_args[0]:
                    value, label = choice[0], choice[1]
                    if isinstance(label, dict):
                        label = label.get("default", None)
                        if label is None:
                            error_msg = "'default' not in choices."
                            raise ValidationError({field_name: error_msg})

                    new_choices.append((value, label))

                field_args[0] = new_choices

        for defn in fields_defn:
            field_class_str = defn["field"]
            field_name = defn["name"]

            field_args = copy.deepcopy(defn.get("field_args", list()))
            field_kwargs = copy.deepcopy(defn.get("field_kwargs", dict()))

            field_class = metacls._get_field_class(
                field_class_str, serializer_classes)

            if field_class in NOT_AVAILABLE_FIELDS:
                e_str = "'{}' field not avalable.".format(field_class_str)
                raise ValidationError({field_name: e_str})

            if not field_class:
                e_str = "Can't find '{}' field class".format(field_class_str)
                raise ValidationError({field_name: e_str})

            _translation_default(field_name, field_class, field_args, field_kwargs)

            validators = _build_validators(defn)
            if validators:
                field_kwargs["validators"] = validators

            try:
                fields[field_name] = field_class(*field_args, **field_kwargs)

            except Exception as e:
                raise ValidationError({field_name: e})

            # Field validate method
            if defn.get("validate_method", None):
                warnings.warn(
                    "'validate_method' will be deprecated in the future. "
                    "Please change to field_validate_method.",
                    PendingDeprecationWarning
                )

            field_validate_method = defn.get(
                "validate_method", None
            ) or defn.get(
                "field_validate_method", None
            )

            if allow_validate_method == False and field_validate_method != None:
                raise ValidationError({
                    field_name: "serializer validate_method not allowed."
                })

            if field_validate_method:
                try:
                    validate_methods.update({
                        field_name: metacls._parse_validate_method(field_validate_method)
                    })

                except Exception as e:
                    e_str = "Can't parse {} field_validate_method: {}".format(
                        field_name, e)
                    raise ValidationError({field_name: e_str})

        return fields, validate_methods

    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return super().__prepare__(name, bases, **kwargs)

    def __new__(metacls, serializer_defn, bases, namespace, **kwargs):

        # serializer_name
        serializer_name = serializer_defn["name"]
        fields_defn = serializer_defn["fields"]
        serializer_classes = kwargs.pop("serializer_classes")
        allow_validate_method = kwargs.pop("allow_validate_method", True)

        # build fields
        fields, field_validate_methods = metacls._build_fields(
            fields_defn, serializer_classes, allow_validate_method)

        # set fields
        namespace.update(fields)

        # field validate methods
        for field_name, validate_method in field_validate_methods.items():
            method_name = "validate_{}".format(field_name)
            namespace.update({method_name: validate_method})

        # serializer validate method
        if serializer_defn.get("validate_method", None):
            warnings.warn(
                "'validate_method' will be deprecated in the future. "
                "Please change to serializer_validate_method.",
                PendingDeprecationWarning
            )

        serializer_validate_method = serializer_defn.get(
            "serializer_validate_method", None
        ) or serializer_defn.get(
            "validate_method", None
        )

        if allow_validate_method == False and serializer_validate_method != None:
            raise ValidationError("serializer validate_method not allowed.")

        if serializer_validate_method:
            try:
                namespace.update({
                    "validate": metacls._parse_validate_method(
                        serializer_validate_method)
                })

            except Exception as e:
                raise ValidationError({
                    field_name: "Can't parse serializer_validate_method: {}".format(e)
                })

        kls = super().__new__(
            metacls, serializer_name, bases, namespace, *kwargs)

        setattr(kls, "serializer_definition_data", serializer_defn)
        return kls

    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace)


def _defn_pre_checker(defn_data):

    def _field_checker(defn):
        check_keys = ("name", "field",)

        for field in defn:
            for key in check_keys:
                if key not in field:
                    raise ValidationError("No {} in field: {}".format(
                        key, pprint.pformat(field)))

    def _serializer_checker(defn):
        check_keys = ("name", "fields",)

        for key in check_keys:
            if key not in defn:
                e_str = "No {} in serializer definition: {}".format(
                    key, pprint.pformat(defn))
                raise ValidationError(e_str)

        _field_checker(defn["fields"])

    if "main" not in defn_data:
        raise ValidationError("Please define a 'main' serializer data")

    _serializer_checker(defn_data["main"])

    if "depending_serializers" in defn_data:
        for defn in defn_data["depending_serializers"]:
            _serializer_checker(defn)


class BaseDefinableSerializer(rf_serializers.Serializer):

    def switch_choice_label_language(self, **kwargs):
        request = kwargs.get("context", {}).get("request", {})
        lang = getattr(request, "LANGUAGE_CODE", get_language())

        for field_defn in self.serializer_definition_data["fields"]:
            field = self.fields[field_defn["name"]]

            # field_kwargs
            for target in ("label", "help_text",):
                value = field_defn.get("field_kwargs", {}).get(target, None)
                if isinstance(value, dict):
                    setattr(field, target, value.get(lang, getattr(field, target, "")))

            # choices
            need_switch_lang = all([
                isinstance(field, rf_serializers.ChoiceField),
                not isinstance(field, rf_serializers.FilePathField)
            ])

            if need_switch_lang:
                new_choice_list = list()

                choice_list = field_defn["field_args"][0]
                for i, choice in enumerate(choice_list):
                    value, choice_label = choice[0], choice[1]
                    if isinstance(choice_list[i][1], dict):
                        choice_label = choice_list[i][1].get(lang, choice_label.get("default"))
                    new_choice_list.append((value, choice_label))

                field._set_choices(new_choice_list)


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.switch_choice_label_language(**kwargs)

def build_serializer(defn_data, allow_validate_method=True):

    def _build_serializer_class(serializer_defn):

        kwargs = {
            "serializer_classes": serializer_classes,
            "allow_validate_method":allow_validate_method,
        }

        serializer_name = serializer_defn["name"]

        namespace = {
            "namespace": DefinableSerializerMeta.__prepare__(
                serializer_name, _base_classes, **kwargs)
        }

        return DefinableSerializerMeta(
            serializer_defn, _base_classes, **namespace, **kwargs)

    _base_classes = (BaseDefinableSerializer,)
    serializer_classes = dict()

    _defn_pre_checker(defn_data)

    main_defn = defn_data.get("main")
    depending_defn = defn_data.get("depending_serializers", list())

    main_serializer = None

    # build depending_serializers
    try:
        for defn in depending_defn:
            serializer_classes[defn["name"]] = _build_serializer_class(defn)

        # build main serializer
        main_serializer = _build_serializer_class(main_defn)

    except Exception as e:<commit_msg>refacotring<commit_after>"""
Copyright 2017 salexkidd

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from django.utils.translation import ugettext as _
from django.core.exceptions import ValidationError
from django.utils.translation import get_language

from rest_framework import serializers as rf_serializers

import simplejson
import codecs
import pprint
import pydoc
import types
import yaml
import warnings
import copy

NOT_AVAILABLE_FIELDS = (
    rf_serializers.ListField,
    rf_serializers.DictField,
    rf_serializers.SerializerMethodField,
)


__all__ = (
    "build_serializer",
    "build_serializer_by_json",
    "build_serializer_by_json_file",
    "build_serializer_by_yaml",
    "build_serializer_by_yaml_file",
)


class DefinableSerializerMeta(rf_serializers.SerializerMetaclass):
    # https://stackoverflow.com/questions/27258557/metaclass-arguments-for-python-3-x

    @classmethod
    def _parse_validate_method(metacls, method_str):
        global_var, local_var = dict(), dict()

        try:
            exec(method_str, global_var, local_var)
            validate_method = local_var["validate_method"]

        except Exception as e:
            raise ValidationError(e)

        if not isinstance(validate_method, types.FunctionType):
            raise ValidationError("Not a function")

        return validate_method

    @classmethod
    def _get_field_class(metacls, field_class_str, serializer_classes):

        # get from Django Restframework serializers
        field_class = getattr(rf_serializers, field_class_str, None)

        # get from builded depending serializers
        if not field_class:
            field_class = serializer_classes.get(field_class_str, None)

        # You can get "<< package>>.<< module >>.<< class >>" string
        if not field_class:
            field_class = pydoc.locate(field_class_str)

        return field_class

    @classmethod
    def _build_fields(metacls, fields_defn, serializer_classes, allow_validate_method):
        fields = dict()
        validate_methods = dict()

        def _build_validators(defn):
            field_name = defn["name"]
            validators = list()

            for validator_defn in defn.get("validators", list()):
                validator_class_path = validator_defn["validator"]
                validator_class = pydoc.locate(validator_class_path)

                if not validator_class:
                    raise ValidationError({
                        field_name: "cannot import name '{}'".format(
                            validator_class_path
                        )
                    })

                try:
                    validators.append(
                        validator_class(
                            *validator_defn.get("args", list()),
                            **validator_defn.get("kwargs", dict())
                        )
                    )
                except Exception as e:
                    raise ValidationError({field_name: e})

            return validators

        def _set_to_default_string(field_name, field_class, field_args, field_kwargs):
            """
            Will change each attribute string to 'default' string if give translation text
            """
            for target in ("label", "help_text",):
                value = field_kwargs.get(target, None)
                if isinstance(value, dict):
                    value = value.get("default", None)
                    if value is None:
                        error_msg = "'default' not in '{}'".format(target)
                        raise ValidationError({field_name: error_msg})
                    field_kwargs[target] = value


            # Choices
            need_switch_to_default = all([
                issubclass(field_class, rf_serializers.ChoiceField),
                not issubclass(field_class, rf_serializers.FilePathField)])

            if need_switch_to_default:
                new_choices = list()
                for choice in field_args[0]:
                    value, label = choice[0], choice[1]
                    if isinstance(label, dict):
                        label = label.get("default", None)
                        if label is None:
                            error_msg = "'default' is required in choices."
                            raise ValidationError({field_name: error_msg})
                    new_choices.append((value, label))

                field_args[0] = new_choices

        for defn in fields_defn:
            field_class_str = defn["field"]
            field_name = defn["name"]

            field_args = copy.deepcopy(defn.get("field_args", list()))
            field_kwargs = copy.deepcopy(defn.get("field_kwargs", dict()))

            field_class = metacls._get_field_class(
                field_class_str, serializer_classes)

            if field_class in NOT_AVAILABLE_FIELDS:
                e_str = "'{}' field not avalable.".format(field_class_str)
                raise ValidationError({field_name: e_str})

            if not field_class:
                e_str = "Can't find '{}' field class".format(field_class_str)
                raise ValidationError({field_name: e_str})

            _set_to_default_string(field_name, field_class, field_args, field_kwargs)

            validators = _build_validators(defn)
            if validators:
                field_kwargs["validators"] = validators

            try:
                fields[field_name] = field_class(*field_args, **field_kwargs)

            except Exception as e:
                raise ValidationError({field_name: e})

            # Field validate method
            if defn.get("validate_method", None):
                warnings.warn(
                    "'validate_method' will be deprecated in the future. "
                    "Please change to field_validate_method.",
                    PendingDeprecationWarning
                )

            field_validate_method = defn.get(
                "validate_method", None
            ) or defn.get(
                "field_validate_method", None
            )

            allow_validate_method_violation = all([
                allow_validate_method is False,
                field_validate_method is not None,
            ])

            if allow_validate_method_violation:
                raise ValidationError({
                    field_name: "serializer validate_method not allowed."
                })

            if field_validate_method:
                try:
                    validate_methods.update({
                        field_name: metacls._parse_validate_method(field_validate_method)
                    })

                except Exception as e:
                    e_str = "Can't parse {} field_validate_method: {}".format(
                        field_name, e)
                    raise ValidationError({field_name: e_str})

        return fields, validate_methods

    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return super().__prepare__(name, bases, **kwargs)

    def __new__(metacls, serializer_defn, bases, namespace, **kwargs):

        # serializer_name
        serializer_name = serializer_defn["name"]
        fields_defn = serializer_defn["fields"]
        serializer_classes = kwargs.pop("serializer_classes")
        allow_validate_method = kwargs.pop("allow_validate_method", True)

        # build fields
        fields, field_validate_methods = metacls._build_fields(
            fields_defn, serializer_classes, allow_validate_method)

        # set fields
        namespace.update(fields)

        # field validate methods
        for field_name, validate_method in field_validate_methods.items():
            method_name = "validate_{}".format(field_name)
            namespace.update({method_name: validate_method})

        # serializer validate method
        if serializer_defn.get("validate_method", None):
            warnings.warn(
                "'validate_method' will be deprecated in the future. "
                "Please change to serializer_validate_method.",
                PendingDeprecationWarning
            )

        serializer_validate_method = serializer_defn.get(
            "serializer_validate_method", None
        ) or serializer_defn.get(
            "validate_method", None
        )

        allow_validate_method_violation = all([
            allow_validate_method is False,
            serializer_validate_method is not None
        ])

        if allow_validate_method_violation:
            raise ValidationError("serializer validate_method not allowed.")

        if serializer_validate_method:
            try:
                namespace.update({
                    "validate": metacls._parse_validate_method(serializer_validate_method)
                })

            except Exception as e:
                raise ValidationError({
                    field_name: "Can't parse serializer_validate_method: {}".format(e)
                })

        kls = super().__new__(
            metacls, serializer_name, bases, namespace, *kwargs)

        setattr(kls, "serializer_definition_data", serializer_defn)
        return kls

    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace)


def _defn_pre_checker(defn_data):

    def _field_checker(defn):
        check_keys = ("name", "field",)

        for field in defn:
            for key in check_keys:
                if key not in field:
                    raise ValidationError("No {} in field: {}".format(
                        key, pprint.pformat(field)))

    def _serializer_checker(defn):
        check_keys = ("name", "fields",)

        for key in check_keys:
            if key not in defn:
                e_str = "No {} in serializer definition: {}".format(
                    key, pprint.pformat(defn))
                raise ValidationError(e_str)

        _field_checker(defn["fields"])

    if "main" not in defn_data:
        raise ValidationError("Please define a 'main' serializer data")

    _serializer_checker(defn_data["main"])

    if "depending_serializers" in defn_data:
        for defn in defn_data["depending_serializers"]:
            _serializer_checker(defn)


class BaseDefinableSerializer(rf_serializers.Serializer):

    def switch_to_language_str(self, **kwargs):
        request = kwargs.get("context", {}).get("request", {})
        lang = getattr(request, "LANGUAGE_CODE", get_language())

        for field_defn in self.serializer_definition_data["fields"]:
            field = self.fields[field_defn["name"]]

            # field_kwargs
            for target in ("label", "help_text",):
                value = field_defn.get("field_kwargs", {}).get(target, None)
                if isinstance(value, dict):
                    setattr(field, target, value.get(lang, getattr(field, target, "")))

            # choices
            need_switch_lang = all([
                isinstance(field, rf_serializers.ChoiceField),
                not isinstance(field, rf_serializers.FilePathField)
            ])

            if need_switch_lang:
                new_choice_list = list()
                choice_list = field_defn["field_args"][0]
                for i, choice in enumerate(choice_list):
                    value, choice_label = choice[0], choice[1]
                    if isinstance(choice_list[i][1], dict):
                        choice_label = choice_list[i][1].get(lang, choice_label.get("default"))
                    new_choice_list.append((value, choice_label))
                field._set_choices(new_choice_list)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.switch_to_language_str(**kwargs)


def build_serializer(defn_data, allow_validate_method=True):

    def _build_serializer_class(serializer_defn):

        kwargs = {
            "serializer_classes": serializer_classes,
            "allow_validate_method": allow_validate_method,
        }

        serializer_name = serializer_defn["name"]

        namespace = {
            "namespace": DefinableSerializerMeta.__prepare__(
                serializer_name, _base_classes, **kwargs)
        }

        return DefinableSerializerMeta(
            serializer_defn, _base_classes, **namespace, **kwargs)

    _base_classes = (BaseDefinableSerializer,)
    serializer_classes = dict()

    _defn_pre_checker(defn_data)

    main_defn = defn_data.get("main")
    depending_defn = defn_data.get("depending_serializers", list())

    main_serializer = None

    # build depending_serializers
    try:
        for defn in depending_defn:
            serializer_classes[defn["name"]] = _build_serializer_class(defn)

        # build main serializer
        main_serializer = _build_serializer_class(main_defn)

    except Exception as e:
{'fraction_non_alphanumeric': 0.08118500073238612, 'fraction_numerical': 0.0016112494507104144, 'mean_word_length': 2.140409383624655, 'pattern_counts': {'":': 8, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 2, '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': '7570508', 'n_tokens_mistral': 7440, 'n_tokens_neox': 7001, 'n_words': 1748}