<commit_before># -*- coding: utf-8 -*-

"""
Classes that define unique objects and the index to store them.
"""


__all__ = ["UniqueObject", "UniqueObjectIndex", "uniqueness_context", "unique_tree"]


import collections
import contextlib

import six

from .util import typed, make_list


_no_default = object()

_uniqueness_context_stack = []


class UniqueObjectMeta(type):
    """
    Meta class definition that adds an instance cache to every class inheriting from
    :py:class:`UniqueObject`.
    """

    def __new__(meta_cls, class_name, bases, class_dict):
        # set default_uniqueness_context to the lower-case class name when not set
        class_dict.setdefault("default_uniqueness_context", class_name.lower())

        # create the class
        cls = super(UniqueObjectMeta, meta_cls).__new__(meta_cls, class_name, bases, class_dict)

        # add an empty instance cache
        cls._instances = {}

        return cls


@six.add_metaclass(UniqueObjectMeta)
class UniqueObject(object):
    """
    An unique object defined by a *name* and an *id*. The purpose of this class is to provide a
    simple interface for objects that are

    1. used programatically and should therefore have a unique, human-readable name, and
    2. have a unique identifier that can be saved to files, such as (e.g.) ROOT trees. 

    Both, *name* and *id* should have unique values on their own per *uniqueness context*
    separately. If *context* is *None*, either the current one as defined with
    :py:func:`uniqueness_context` or, when empty, the class member *default_uniqueness_context* is
    used. Examples:

    .. code-block:: python

       foo = UniqueObject("foo", 1)

       print(foo.name)
       # -> "foo"
       print(foo.id)
       # -> 1

       UniqueObject(123, 1)
       # -> TypeError: invalid name: 123
       UniqueObject("foo", "mystring")
       # -> TypeError: invalid id: mystring

       bar = UniqueObject("foo", 2)
       # -> ValueError: duplicate name in uniqueness context 'default': foo

       bar = UniqueObject("bar", 1)
       # -> ValueError: duplicate id in uniqueness context 'default': 1

       bar = UniqueObject("bar", 1, context="myNewContext")
       # works!

       foo == 1
       # -> True

       bar == "bar"
       # -> True

       foo == bar
       # -> False

    .. py:attribute:: default_uniqueness_context
       type: arbitrary (hashable)
       classmember

       The default context of uniqueness when none is given in the instance constructor. Two
       instances are only allowed to have the same name *or* the same id if their classes have
       different contexts. Defaults to the lower-case name of the inheriting class When not set
       during class creation.

    .. py:attribute:: uniqueness_context
       type: arbitrary (hashable)

       The uniqueness context of this instance.

    .. py:attribute:: name
       type: str
       read-only

       The unique name.

    .. py:attribute:: id
       type: int
       read-only

       The unique id.
    """

    AUTO_ID = "+"

    _instances = {}

    @classmethod
    def get_instance(cls, obj, default=_no_default, context=None):
        """ get_instance(obj, [default], context=None)
        Returns an object that was instantiated in this class before. *obj* might be a *name*, *id*,
        or an instance of *cls*. If *default* is given, it is used as the default return value if no
        such object was be found. Otherwise, an error is raised. *context* defaults to the
        :py:attr:`default_uniqueness_context` if this class.
        """
        if context is None:
            context = cls.default_uniqueness_context

        if context not in cls._instances:
            if default != _no_default:
                return default
            else:
                raise ValueError("unknown context: %s" % (context,))

        return cls._instances[context].get(obj, default=default)

    @classmethod
    def auto_id(cls, name, context):
        """
        Method to create an automatic id for instances that are created with ``id="+"``. The default
        procedure is ``max(ids) + 1``.
        """
        if context not in cls._instances or len(cls._instances[context]) == 0:
            return 1
        else:
            return max(cls._instances[context].ids()) + 1

    def __init__(self, name, id, context=None):
        super(UniqueObject, self).__init__()

        # register empty attributes
        self._uniqueness_context = None
        self._name = None
        self._id = None

        # set the context
        if context is None:
            if _uniqueness_context_stack:
                context = _uniqueness_context_stack[-1]
            else:
                context = self.default_uniqueness_context
        self._uniqueness_context = context

        # register an instance cache if it does not exist yet
        if self.uniqueness_context not in self._instances:
            self._instances[self.uniqueness_context] = UniqueObjectIndex(cls=self.__class__)
        cache = self._instances[self.uniqueness_context]

        # use the typed parser to check the passed name, check for duplicates and store it
        name = self.__class__.name.fparse(self, name)
        if name in cache.names():<commit_msg>Improve default uniqueness context.<commit_after># -*- coding: utf-8 -*-

"""
Classes that define unique objects and the index to store them.
"""


__all__ = ["UniqueObject", "UniqueObjectIndex", "uniqueness_context", "unique_tree"]


import collections
import contextlib

import six

from .util import typed, make_list


_no_default = object()

_uniqueness_context_stack = []


class UniqueObjectMeta(type):
    """
    Meta class definition that adds an instance cache to every class inheriting from
    :py:class:`UniqueObject`.
    """

    def __new__(meta_cls, class_name, bases, class_dict):
        # set default_uniqueness_context to the lower-case class name when not set
        class_dict.setdefault("default_uniqueness_context", class_name.lower())

        # create the class
        cls = super(UniqueObjectMeta, meta_cls).__new__(meta_cls, class_name, bases, class_dict)

        # add an empty instance cache
        cls._instances = {}

        return cls


@six.add_metaclass(UniqueObjectMeta)
class UniqueObject(object):
    """
    An unique object defined by a *name* and an *id*. The purpose of this class is to provide a
    simple interface for objects that are

    1. used programatically and should therefore have a unique, human-readable name, and
    2. have a unique identifier that can be saved to files, such as (e.g.) ROOT trees. 

    Both, *name* and *id* should have unique values on their own per *uniqueness context*
    separately. If *context* is *None*, either the current one as defined with
    :py:func:`uniqueness_context` or, when empty, the class member *default_uniqueness_context* is
    used. Examples:

    .. code-block:: python

       foo = UniqueObject("foo", 1)

       print(foo.name)
       # -> "foo"
       print(foo.id)
       # -> 1

       UniqueObject(123, 1)
       # -> TypeError: invalid name: 123
       UniqueObject("foo", "mystring")
       # -> TypeError: invalid id: mystring

       bar = UniqueObject("foo", 2)
       # -> ValueError: duplicate name in uniqueness context 'uniqueobject': foo

       bar = UniqueObject("bar", 1)
       # -> ValueError: duplicate id in uniqueness context 'uniqueobject': 1

       bar = UniqueObject("bar", 1, context="myNewContext")
       # works!

       foo == 1
       # -> True

       bar == "bar"
       # -> True

       foo == bar
       # -> False

    .. py:attribute:: default_uniqueness_context
       type: arbitrary (hashable)
       classmember

       The default context of uniqueness when none is given in the instance constructor. Two
       instances are only allowed to have the same name *or* the same id if their classes have
       different contexts. This member is not inherited when creating a sub-class.

    .. py:attribute:: uniqueness_context
       type: arbitrary (hashable)

       The uniqueness context of this instance.

    .. py:attribute:: name
       type: str
       read-only

       The unique name.

    .. py:attribute:: id
       type: int
       read-only

       The unique id.
    """

    AUTO_ID = "+"

    _instances = {}

    @classmethod
    def get_default_context(cls):
        if _uniqueness_context_stack:
            return _uniqueness_context_stack[-1]
        else:
            return cls.default_uniqueness_context

    @classmethod
    def get_instance(cls, obj, default=_no_default, context=None):
        """ get_instance(obj, [default], context=None)
        Returns an object that was instantiated in this class before. *obj* might be a *name*, *id*,
        or an instance of *cls*. If *default* is given, it is used as the default return value if no
        such object was be found. Otherwise, an error is raised. *context* defaults to the
        :py:attr:`default_uniqueness_context` if this class.
        """
        if context is None:
            context = cls.get_default_context()

        if context not in cls._instances:
            if default != _no_default:
                return default
            else:
                raise ValueError("unknown context: %s" % (context,))

        return cls._instances[context].get(obj, default=default)

    @classmethod
    def auto_id(cls, name, context):
        """
        Method to create an automatic id for instances that are created with ``id="+"``. The default
        procedure is ``max(ids) + 1``.
        """
        if context not in cls._instances or len(cls._instances[context]) == 0:
            return 1
        else:
            return max(cls._instances[context].ids()) + 1

    def __init__(self, name, id, context=None):
        super(UniqueObject, self).__init__()

        # register empty attributes
        self._uniqueness_context = None
        self._name = None
        self._id = None

        # set the context
        if context is None:
            context = self.get_default_context()
        self._uniqueness_context = context

        # register an instance cache if it does not exist yet
        if self.uniqueness_context not in self._instances:
            self._instances[self.uniqueness_context] = UniqueObjectIndex(cls=self.__class__)
        cache = self._instances[self.uniqueness_context]

        # use the typed parser to check the passed name, check for duplicates and store it
        name = self.__class__.name.fparse(self, name)
        if name in cache.names():
{'fraction_non_alphanumeric': 0.09002822201317028, 'fraction_numerical': 0.004139228598306679, 'mean_word_length': 2.4787303664921465, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, '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': '297678', 'n_tokens_mistral': 2999, 'n_tokens_neox': 2858, 'n_words': 1029}