<commit_before>import copy
from toolz.compatibility import reduce


__all__ = ('assoc_obj', 'update_in_obj', 'get_in_obj')


def assoc_obj(o, attr, value):
    """
    Return a new obj with attr set to value

    Does not modify the initial object.

    >>> class C():
    ...     def __init__(self):
    ...         self.x = 1
    >>> c = C()
    >>> assoc_obj(c, 'x', 2).__dict__
    {'x': 2}
    >>> assoc_obj(c, 'y', 3).__dict__ # doctest: +SKIP
    {'x': 1, 'y': 3}
    """
    o = copy.deepcopy(o)
    setattr(o, attr, value)
    return o


def update_in_obj(o, attrs, func, default=None):
    """ Update value in a (potentially) nested object

    inputs:
    o - object on which to operate
    attrs - list or tuple giving the location of the value to be changed in o
    func - function to operate on that value

    If attrs == [a0, ..., aX] and thread_first(o, (getattr, a0), ...,
    (getattr, aX)) == v, update_in_obj returns a copy of the original object
    with v replaced by func(v), but does not mutate the original object.

    If the current attr is not already in o, and there are no further attrs,
    update_in_obj will set the value of attr to func(default). If there are
    further attrs, however, update_in_obj will raise an AttributeError, rather
    than create nested objects.

    >>> inc = lambda x: x + 1
    >>> class Person():
    ...     def __init__(self, age):
    ...         self.age = age
    >>> class Age():
    ...     def __init__(self, years, days):
    ...         self.years = years
    ...         self.days = days
    >>> alice = Person(Age(30, 100))
    >>> new_alice = update_in_obj(alice, ['age', 'days'], inc)
    >>> new_alice.age.__dict__ == {'days': 101, 'years': 30}
    True
    >>> new_alice = update_in_obj(alice, ['age', 'hours'], int, default=6.5)
    >>> new_alice.age.__dict__ == {'hours': 6, 'days': 100, 'years': 30}
    True
    >>> update_in_obj(alice, ['education', 'college'], str, default='CMU')
    ... # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    AttributeError: ... has no attribute 'education'
    """
    def update(o, attrs, func, default):
        assert len(attrs) > 0
        attr, rest = attrs[0], attrs[1:]
        if rest:
            update(getattr(o, attr), rest, func, default)
        else:
            setattr(o, attr, func(getattr(o, attr, default)))

    o = copy.deepcopy(o)
    update(o, attrs, func, default)
    return o


def get_in_obj(attrs, o, default=None, no_default=False):
    """<commit_msg>don't deepcopy<commit_after>import copy
from toolz.compatibility import reduce


__all__ = ('assoc_obj', 'update_in_obj', 'get_in_obj')


def assoc_obj(o, attr, value):
    """
    Return a new obj with attr set to value

    Does not modify the initial object.

    >>> class C():
    ...     def __init__(self):
    ...         self.x = 1
    >>> c = C()
    >>> assoc_obj(c, 'x', 2).__dict__
    {'x': 2}
    >>> assoc_obj(c, 'y', 3).__dict__ # doctest: +SKIP
    {'x': 1, 'y': 3}
    """
    o = copy.copy(o)
    setattr(o, attr, value)
    return o


def update_in_obj(o, attrs, func, default=None):
    """ Update value in a (potentially) nested object

    inputs:
    o - object on which to operate
    attrs - list or tuple giving the location of the value to be changed in o
    func - function to operate on that value

    If attrs == [a0, ..., aX] and thread_first(o, (getattr, a0), ...,
    (getattr, aX)) == v, update_in_obj returns a copy of the original object
    with v replaced by func(v), but does not mutate the original object.

    If the current attr is not already in o, and there are no further attrs,
    update_in_obj will set the value of attr to func(default). If there are
    further attrs, however, update_in_obj will raise an AttributeError, rather
    than create nested objects.

    >>> inc = lambda x: x + 1
    >>> class Person():
    ...     def __init__(self, age):
    ...         self.age = age
    >>> class Age():
    ...     def __init__(self, years, days):
    ...         self.years = years
    ...         self.days = days
    >>> alice = Person(Age(30, 100))
    >>> new_alice = update_in_obj(alice, ['age', 'days'], inc)
    >>> new_alice.age.__dict__ == {'days': 101, 'years': 30}
    True
    >>> new_alice = update_in_obj(alice, ['age', 'hours'], int, default=6.5)
    >>> new_alice.age.__dict__ == {'hours': 6, 'days': 100, 'years': 30}
    True
    >>> update_in_obj(alice, ['education', 'college'], str, default='CMU')
    ... # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    AttributeError: ... has no attribute 'education'
    """
    attr, rest = attrs[0], attrs[1:]
    if rest:
        return assoc_obj(o, attr,
                         update_in_obj(getattr(o, attr), rest, func, default))
    else:
        return assoc_obj(o, attr, func(getattr(o, attr, default)))


def get_in_obj(attrs, o, default=None, no_default=False):
    """
{'fraction_non_alphanumeric': 0.14764694001211875, 'fraction_numerical': 0.011916784487982226, 'mean_word_length': 2.539671193709793, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 82, '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': '3052464', 'n_tokens_mistral': 1703, 'n_tokens_neox': 1624, 'n_words': 539}