<commit_before>from ..core import DataDescriptor
from ..utils.importer import import_object


class ClassPath(DataDescriptor):

    def __init__(self, default, prefix=None):
        super(ClassPath, self).__init__(default=default)
        self.prefix = prefix

    def verify(self, obj, value, myname=None):
        ret = super(ClassPath, self).verify(obj, value, myname)
        self.getclass(obj)
        return ret

    def getclass(self, obj):
        path = self.get(obj)
        if path.startswith('.'):
            if self.prefix is None:
                raise ValueError('relative import path is specified but no'
                                 ' prefix is defined.')
            path = self.prefix + path
        return import_object(path)


class ClassPlaceholder(DataDescriptor):

    def __init__(self, cpath, **kwds):
        super(ClassPlaceholder, self).__init__(**kwds)
        self.cpath = cpath

    def get(self, obj):
        value = super(ClassPlaceholder, self).get(obj)
        cls = self.cpath.getclass(obj)
        if isinstance(value, cls):
            return value

        newval = cls(value)
        self.__set__(obj, newval)
        return newval


def dynamic_class(path, prefix=None, **kwds):
    """
    Dynamic class loading helper.

    >>> from compapp import Parametric, dynamic_class
    >>> class MyApp(Parametric):
    ...     obj, path = dynamic_class('.ClassA', prefix=__name__, default={})
    ...
    >>> class ClassA:
    ...     def __init__(self, params):
    ...         self.params = params
    ...
    >>> class ClassB(ClassA):
    ...     pass
    ...

    .. hack to make it work in doctest:
       >>> import sys
       >>> mod = sys.modules[__name__]
       >>> mod.ClassA = ClassA
       >>> mod.ClassB = ClassB

    >>> app = MyApp()
    >>> app.obj                                        # doctest: +ELLIPSIS
    <....ClassA ... at 0x...><commit_msg>Allow no-default parameter in dynamic_class<commit_after>from ..core import Unspecified, DataDescriptor
from ..utils.importer import import_object


class ClassPath(DataDescriptor):

    def __init__(self, default, prefix=None):
        super(ClassPath, self).__init__(default=default)
        self.prefix = prefix

    def verify(self, obj, value, myname=None):
        ret = super(ClassPath, self).verify(obj, value, myname)
        self.getclass(obj)
        return ret

    def getclass(self, obj):
        path = self.get(obj)
        if path.startswith('.'):
            if self.prefix is None:
                raise ValueError('relative import path is specified but no'
                                 ' prefix is defined.')
            path = self.prefix + path
        return import_object(path)


class ClassPlaceholder(DataDescriptor):

    def __init__(self, cpath, **kwds):
        super(ClassPlaceholder, self).__init__(**kwds)
        self.cpath = cpath

    def get(self, obj):
        value = super(ClassPlaceholder, self).get(obj)
        cls = self.cpath.getclass(obj)
        if isinstance(value, cls):
            return value

        newval = cls() if value is Unspecified else cls(value)
        self.__set__(obj, newval)
        return newval


def dynamic_class(path, prefix=None, **kwds):
    """
    Dynamic class loading helper.

    >>> from compapp import Parametric, dynamic_class
    >>> class MyApp(Parametric):
    ...     obj, path = dynamic_class('.ClassA', prefix=__name__, default={})
    ...
    >>> class ClassA:
    ...     def __init__(self, params):
    ...         self.params = params
    ...
    >>> class ClassB(ClassA):
    ...     pass
    ...

    .. hack to make it work in doctest:
       >>> import sys
       >>> mod = sys.modules[__name__]
       >>> mod.ClassA = ClassA
       >>> mod.ClassB = ClassB

    >>> app = MyApp()
    >>> app.obj                                        # doctest: +ELLIPSIS
    <....ClassA ... at 0x...>
{'fraction_non_alphanumeric': 0.12474332648870637, 'fraction_numerical': 0.000513347022587269, 'mean_word_length': 2.0139211136890953, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4536812', 'n_tokens_mistral': 1129, 'n_tokens_neox': 1103, 'n_words': 290}