<commit_before># -*- coding: utf-8 -*-
import copy
import six

from odin import bases
from odin import exceptions, registration
from odin.exceptions import ValidationError
from odin.fields import NOT_PROVIDED
from odin.utils import cached_property, field_iter_items, force_tuple, getmeta

DEFAULT_TYPE_FIELD = '$'


class ResourceOptions(object):
    META_OPTION_NAMES = (
        'name', 'namespace', 'name_space', 'verbose_name', 'verbose_name_plural', 'abstract', 'doc_group',
        'type_field', 'key_field_name', 'key_field_names', 'field_sorting'
    )

    def __init__(self, meta):
        self.meta = meta
        self.parents = []
        self.fields = []
        self._key_fields = []
        self.virtual_fields = []

        self.name = None
        self.class_name = None
        self.name_space = NOT_PROVIDED
        self.verbose_name = None
        self.verbose_name_plural = None
        self.abstract = False
        self.doc_group = None
        self.type_field = DEFAULT_TYPE_FIELD
        self.key_field_names = None
        self.field_sorting = False

        self._cache = {}

    def __repr__(self):
        return '<Options for %s>' % self.resource_name

    def contribute_to_class(self, cls, _):
        cls._meta = self
        self.name = cls.__name__
        self.class_name = "%s.%s" % (cls.__module__, cls.__name__)

        if self.meta:
            meta_attrs = self.meta.__dict__.copy()
            for name in self.meta.__dict__:
                if name.startswith('_'):
                    del meta_attrs[name]

            for attr_name in self.META_OPTION_NAMES:
                if attr_name in meta_attrs:
                    # Allow meta to be defined as namespace
                    if attr_name == 'namespace':
                        setattr(self, 'name_space', meta_attrs.pop(attr_name))
                    # Allow key_field_names to be defined as key_field_name
                    elif attr_name == 'key_field_name':
                        setattr(self, 'key_field_names', meta_attrs.pop(attr_name))
                    else:
                        setattr(self, attr_name, meta_attrs.pop(attr_name))
                elif hasattr(self.meta, attr_name):
                    setattr(self, attr_name, getattr(self.meta, attr_name))

            # Any leftover attributes must be invalid.
            if meta_attrs != {}:
                raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
        del self.meta

        # Ensure key fields is a tuple
        self.key_field_names = force_tuple(self.key_field_names)

        if not self.verbose_name:
            self.verbose_name = self.name.replace('_', ' ').strip('_ ')
        if not self.verbose_name_plural:
            self.verbose_name_plural = self.verbose_name + 's'

    def _add_key_field(self, field):
        self._key_fields.append(field)

    def add_field(self, field):
        self.fields.append(field)
        if field.key:
            self._add_key_field(field)
        cached_property.clear_caches(self)

    def add_virtual_field(self, field):
        self.virtual_fields.append(field)
        if field.key:
            self._add_key_field(field)
        cached_property.clear_caches(self)

    @property
    def resource_name(self):
        """
        Full name of resource including namespace (if specified)
        """
        if self.name_space:
            return "%s.%s" % (self.name_space, self.name)
        else:
            return self.name

    @cached_property
    def all_fields(self):
        """
        All fields both standard and virtual.
        """
        return self.fields + self.virtual_fields

    @cached_property
    def composite_fields(self):
        """
        All composite fields.
        """
        # Not the nicest solution but is a fairly safe way of detecting a composite field.
        return [f for f in self.fields if (hasattr(f, 'of') and issubclass(f.of, Resource))]

    @cached_property
    def container_fields(self):
        """
        All composite fields with the container flag.

        Used by XML like codecs.

        """
        return [f for f in self.composite_fields if getattr(f, 'use_container', False)]

    @cached_property
    def field_map(self):
        return dict((f.attname, f) for f in self.fields)

    @cached_property
    def parent_resource_names(self):
        """
        List of parent resource names.
        """
        return [getmeta(p).resource_name for p in self.parents]

    @cached_property
    def attribute_fields(self):
        """
        List of fields where is_attribute is True.
        """
        return [f for f in self.fields if f.is_attribute]

    @cached_property
    def element_fields(self):
        """
        List of fields where is_attribute is False.
        """
        return [f for f in self.fields if not f.is_attribute]

    @cached_property
    def element_field_map(self):
        return dict((f.attname, f) for f in self.element_fields)

    @cached_property
    def key_field(self):
        """
        Field specified as the key field
        """
        if self.key_fields:
            return self.key_fields[0]

    @cached_property
    def key_fields(self):
        """
        Tuple of fields specified as the key fields
        """
        fields = []

        # Key fields named in meta go first
        if self.key_field_names:
            for field_name in self.key_field_names:
                fields.append(self.field_map[field_name])

        # Move over any fields defined as keys
        if self._key_fields:
            for field in sorted(self._key_fields, key=hash):
                if field.attname not in self.key_field_names:
                    fields.append(field)

        return fields

    def check(self):<commit_msg>Added readonly_fields set, restructured ResourceOptions.contribute_to_class to match work in Proxy<commit_after># -*- coding: utf-8 -*-
import copy
import six

from odin import bases
from odin import exceptions, registration
from odin.exceptions import ValidationError
from odin.fields import NOT_PROVIDED
from odin.utils import cached_property, field_iter_items, force_tuple, getmeta

DEFAULT_TYPE_FIELD = '$'


class ResourceOptions(object):
    META_OPTION_NAMES = (
        'name', 'namespace', 'name_space', 'verbose_name', 'verbose_name_plural', 'abstract', 'doc_group',
        'type_field', 'key_field_name', 'key_field_names', 'field_sorting'
    )

    def __init__(self, meta):
        self.meta = meta
        self.parents = []

        self.fields = []
        self._key_fields = []
        self.virtual_fields = []

        self.name = None
        self.class_name = None
        self.name_space = NOT_PROVIDED
        self.verbose_name = None
        self.verbose_name_plural = None
        self.abstract = False
        self.doc_group = None
        self.type_field = DEFAULT_TYPE_FIELD
        self.key_field_names = None
        self.field_sorting = False

        self._cache = {}

    def __repr__(self):
        return '<Options for %s>' % self.resource_name

    def contribute_to_class(self, cls, _):
        cls._meta = self
        self.name = cls.__name__
        self.class_name = "%s.%s" % (cls.__module__, cls.__name__)

        if self.meta:
            meta_attrs = self.meta.__dict__.copy()
            for name in self.meta.__dict__:
                if name.startswith('_'):
                    del meta_attrs[name]

            for attr_name in self.META_OPTION_NAMES:
                if attr_name in meta_attrs:
                    value = meta_attrs.pop(attr_name)

                    # Allow meta to be defined as namespace
                    if attr_name == 'namespace':
                        attr_name = 'name_space'

                    # Allow key_field_names to be defined as key_field_name
                    elif attr_name == 'key_field_name':
                        attr_name = 'key_field_names'
                        value = [value]

                    setattr(self, attr_name, value)

                elif hasattr(self.meta, attr_name):
                    setattr(self, attr_name, getattr(self.meta, attr_name))

            # Any leftover attributes must be invalid.
            if meta_attrs != {}:
                raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
        del self.meta

        # Ensure key fields is a tuple
        self.key_field_names = force_tuple(self.key_field_names)

        if not self.verbose_name:
            self.verbose_name = self.name.replace('_', ' ').strip('_ ')
        if not self.verbose_name_plural:
            self.verbose_name_plural = self.verbose_name + 's'

    def _add_key_field(self, field):
        self._key_fields.append(field)

    def add_field(self, field):
        self.fields.append(field)
        if field.key:
            self._add_key_field(field)
        cached_property.clear_caches(self)

    def add_virtual_field(self, field):
        self.virtual_fields.append(field)
        if field.key:
            self._add_key_field(field)
        cached_property.clear_caches(self)

    @property
    def resource_name(self):
        """
        Full name of resource including namespace (if specified)
        """
        if self.name_space:
            return "%s.%s" % (self.name_space, self.name)
        else:
            return self.name

    @cached_property
    def all_fields(self):
        """
        All fields both standard and virtual.
        """
        return self.fields + self.virtual_fields

    @cached_property
    def composite_fields(self):
        """
        All composite fields.
        """
        # Not the nicest solution but is a fairly safe way of detecting a composite field.
        return [f for f in self.fields if (hasattr(f, 'of') and issubclass(f.of, Resource))]

    @cached_property
    def container_fields(self):
        """
        All composite fields with the container flag.

        Used by XML like codecs.

        """
        return [f for f in self.composite_fields if getattr(f, 'use_container', False)]

    @cached_property
    def field_map(self):
        return dict((f.attname, f) for f in self.fields)

    @cached_property
    def parent_resource_names(self):
        """
        List of parent resource names.
        """
        return [getmeta(p).resource_name for p in self.parents]

    @cached_property
    def attribute_fields(self):
        """
        List of fields where is_attribute is True.
        """
        return [f for f in self.fields if f.is_attribute]

    @cached_property
    def element_fields(self):
        """
        List of fields where is_attribute is False.
        """
        return [f for f in self.fields if not f.is_attribute]

    @cached_property
    def element_field_map(self):
        return dict((f.attname, f) for f in self.element_fields)

    @cached_property
    def key_field(self):
        """
        Field specified as the key field
        """
        if self.key_fields:
            return self.key_fields[0]

    @cached_property
    def key_fields(self):
        """
        Tuple of fields specified as the key fields
        """
        fields = []

        # Key fields named in meta go first
        if self.key_field_names:
            for field_name in self.key_field_names:
                fields.append(self.field_map[field_name])

        # Move over any fields defined as keys
        if self._key_fields:
            for field in sorted(self._key_fields, key=hash):
                if field.attname not in self.key_field_names:
                    fields.append(field)

        return fields

    @cached_property
    def readonly_fields(self):
        """
        Fields that can only be read from.
        """
        return self.virtual_fields

    def check(self):
{'fraction_non_alphanumeric': 0.08936241893371515, 'fraction_numerical': 0.000336898846121452, 'mean_word_length': 2.018301982714794, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, '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': '1409719', 'n_tokens_mistral': 3436, 'n_tokens_neox': 3326, 'n_words': 887}