File: utils.py

package info (click to toggle)
factory-boy 3.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 996 kB
  • sloc: python: 7,316; makefile: 107; sh: 24
file content (105 lines) | stat: -rw-r--r-- 3,106 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# Copyright: See the LICENSE file.


import collections
import importlib


def import_object(module_name, attribute_name):
    """Import an object from its absolute path.

    Example:
        >>> import_object('datetime', 'datetime')
        <type 'datetime.datetime'>
    """
    module = importlib.import_module(module_name)
    return getattr(module, attribute_name)


class log_pprint:
    """Helper for properly printing args / kwargs passed to an object.

    Since it is only used with factory.debug(), the computation is
    performed lazily.
    """
    __slots__ = ['args', 'kwargs']

    def __init__(self, args=(), kwargs=None):
        self.args = args
        self.kwargs = kwargs or {}

    def __repr__(self):
        return repr(str(self))

    def __str__(self):
        return ', '.join(
            [
                repr(arg) for arg in self.args
            ] + [
                '%s=%s' % (key, repr(value))
                for key, value in self.kwargs.items()
            ]
        )


class ResetableIterator:
    """An iterator wrapper that can be 'reset()' to its start."""
    def __init__(self, iterator, **kwargs):
        super().__init__(**kwargs)
        self.iterator = iter(iterator)
        self.past_elements = collections.deque()
        self.next_elements = collections.deque()

    def __iter__(self):
        while True:
            if self.next_elements:
                yield self.next_elements.popleft()
            else:
                try:
                    value = next(self.iterator)
                except StopIteration:
                    break
                else:
                    self.past_elements.append(value)
                    yield value

    def reset(self):
        self.next_elements.clear()
        self.next_elements.extend(self.past_elements)


class OrderedBase:
    """Marks a class as being ordered.

    Each instance (even from subclasses) will share a global creation counter.
    """

    CREATION_COUNTER_FIELD = '_creation_counter'

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if type(self) is not OrderedBase:
            self.touch_creation_counter()

    def touch_creation_counter(self):
        bases = type(self).__mro__
        root = bases[bases.index(OrderedBase) - 1]
        if not hasattr(root, self.CREATION_COUNTER_FIELD):
            setattr(root, self.CREATION_COUNTER_FIELD, 0)
        next_counter = getattr(root, self.CREATION_COUNTER_FIELD)
        setattr(self, self.CREATION_COUNTER_FIELD, next_counter)
        setattr(root, self.CREATION_COUNTER_FIELD, next_counter + 1)


def sort_ordered_objects(items, getter=lambda x: x):
    """Sort an iterable of OrderedBase instances.

    Args:
        items (iterable): the objects to sort
        getter (callable or None): a function to extract the OrderedBase instance from an object.

    Examples:
        >>> sort_ordered_objects([x, y, z])
        >>> sort_ordered_objects(v.items(), getter=lambda e: e[1])
    """
    return sorted(items, key=lambda x: getattr(getter(x), OrderedBase.CREATION_COUNTER_FIELD, -1))