xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/namedtuple_with_abc.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# http://code.activestate.com/recipes/577629-namedtupleabc-abstract-base-class-mix-in-for-named/
2*4882a593Smuzhiyun# Copyright (c) 2011 Jan Kaliszewski (zuo). Available under the MIT License.
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# SPDX-License-Identifier: MIT
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun"""
8*4882a593Smuzhiyunnamedtuple_with_abc.py:
9*4882a593Smuzhiyun* named tuple mix-in + ABC (abstract base class) recipe,
10*4882a593Smuzhiyun* works under Python 2.6, 2.7 as well as 3.x.
11*4882a593Smuzhiyun
12*4882a593SmuzhiyunImport this module to patch collections.namedtuple() factory function
13*4882a593Smuzhiyun-- enriching it with the 'abc' attribute (an abstract base class + mix-in
14*4882a593Smuzhiyunfor named tuples) and decorating it with a wrapper that registers each
15*4882a593Smuzhiyunnewly created named tuple as a subclass of namedtuple.abc.
16*4882a593Smuzhiyun
17*4882a593SmuzhiyunHow to import:
18*4882a593Smuzhiyun    import collections, namedtuple_with_abc
19*4882a593Smuzhiyunor:
20*4882a593Smuzhiyun    import namedtuple_with_abc
21*4882a593Smuzhiyun    from collections import namedtuple
22*4882a593Smuzhiyun    # ^ in this variant you must import namedtuple function
23*4882a593Smuzhiyun    #   *after* importing namedtuple_with_abc module
24*4882a593Smuzhiyunor simply:
25*4882a593Smuzhiyun    from namedtuple_with_abc import namedtuple
26*4882a593Smuzhiyun
27*4882a593SmuzhiyunSimple usage example:
28*4882a593Smuzhiyun    class Credentials(namedtuple.abc):
29*4882a593Smuzhiyun        _fields = 'username password'
30*4882a593Smuzhiyun        def __str__(self):
31*4882a593Smuzhiyun            return ('{0.__class__.__name__}'
32*4882a593Smuzhiyun                    '(username={0.username}, password=...)'.format(self))
33*4882a593Smuzhiyun    print(Credentials("alice", "Alice's password"))
34*4882a593Smuzhiyun
35*4882a593SmuzhiyunFor more advanced examples -- see below the "if __name__ == '__main__':".
36*4882a593Smuzhiyun"""
37*4882a593Smuzhiyun
38*4882a593Smuzhiyunimport collections
39*4882a593Smuzhiyunfrom abc import ABCMeta, abstractproperty
40*4882a593Smuzhiyunfrom functools import wraps
41*4882a593Smuzhiyunfrom sys import version_info
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun__all__ = ('namedtuple',)
44*4882a593Smuzhiyun_namedtuple = collections.namedtuple
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun
47*4882a593Smuzhiyunclass _NamedTupleABCMeta(ABCMeta):
48*4882a593Smuzhiyun    '''The metaclass for the abstract base class + mix-in for named tuples.'''
49*4882a593Smuzhiyun    def __new__(mcls, name, bases, namespace):
50*4882a593Smuzhiyun        fields = namespace.get('_fields')
51*4882a593Smuzhiyun        for base in bases:
52*4882a593Smuzhiyun            if fields is not None:
53*4882a593Smuzhiyun                break
54*4882a593Smuzhiyun            fields = getattr(base, '_fields', None)
55*4882a593Smuzhiyun        if not isinstance(fields, abstractproperty):
56*4882a593Smuzhiyun            basetuple = _namedtuple(name, fields)
57*4882a593Smuzhiyun            bases = (basetuple,) + bases
58*4882a593Smuzhiyun            namespace.pop('_fields', None)
59*4882a593Smuzhiyun            namespace.setdefault('__doc__', basetuple.__doc__)
60*4882a593Smuzhiyun            namespace.setdefault('__slots__', ())
61*4882a593Smuzhiyun        return ABCMeta.__new__(mcls, name, bases, namespace)
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun
64*4882a593Smuzhiyunclass _NamedTupleABC(metaclass=_NamedTupleABCMeta):
65*4882a593Smuzhiyun    '''The abstract base class + mix-in for named tuples.'''
66*4882a593Smuzhiyun    _fields = abstractproperty()
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun_namedtuple.abc = _NamedTupleABC
70*4882a593Smuzhiyun#_NamedTupleABC.register(type(version_info))  # (and similar, in the future...)
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun@wraps(_namedtuple)
73*4882a593Smuzhiyundef namedtuple(*args, **kwargs):
74*4882a593Smuzhiyun    '''Named tuple factory with namedtuple.abc subclass registration.'''
75*4882a593Smuzhiyun    cls = _namedtuple(*args, **kwargs)
76*4882a593Smuzhiyun    _NamedTupleABC.register(cls)
77*4882a593Smuzhiyun    return cls
78*4882a593Smuzhiyun
79*4882a593Smuzhiyuncollections.namedtuple = namedtuple
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun
84*4882a593Smuzhiyunif __name__ == '__main__':
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun    '''Examples and explanations'''
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    # Simple usage
89*4882a593Smuzhiyun
90*4882a593Smuzhiyun    class MyRecord(namedtuple.abc):
91*4882a593Smuzhiyun        _fields = 'x y z'  # such form will be transformed into ('x', 'y', 'z')
92*4882a593Smuzhiyun        def _my_custom_method(self):
93*4882a593Smuzhiyun            return list(self._asdict().items())
94*4882a593Smuzhiyun    # (the '_fields' attribute belongs to the named tuple public API anyway)
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun    rec = MyRecord(1, 2, 3)
97*4882a593Smuzhiyun    print(rec)
98*4882a593Smuzhiyun    print(rec._my_custom_method())
99*4882a593Smuzhiyun    print(rec._replace(y=222))
100*4882a593Smuzhiyun    print(rec._replace(y=222)._my_custom_method())
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun    # Custom abstract classes...
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun    class MyAbstractRecord(namedtuple.abc):
105*4882a593Smuzhiyun        def _my_custom_method(self):
106*4882a593Smuzhiyun            return list(self._asdict().items())
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun    try:
109*4882a593Smuzhiyun        MyAbstractRecord()  # (abstract classes cannot be instantiated)
110*4882a593Smuzhiyun    except TypeError as exc:
111*4882a593Smuzhiyun        print(exc)
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun    class AnotherAbstractRecord(MyAbstractRecord):
114*4882a593Smuzhiyun        def __str__(self):
115*4882a593Smuzhiyun            return '<<<{0}>>>'.format(super(AnotherAbstractRecord,
116*4882a593Smuzhiyun                                            self).__str__())
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun    # ...and their non-abstract subclasses
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun    class MyRecord2(MyAbstractRecord):
121*4882a593Smuzhiyun        _fields = 'a, b'
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun    class MyRecord3(AnotherAbstractRecord):
124*4882a593Smuzhiyun        _fields = 'p', 'q', 'r'
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun    rec2 = MyRecord2('foo', 'bar')
127*4882a593Smuzhiyun    print(rec2)
128*4882a593Smuzhiyun    print(rec2._my_custom_method())
129*4882a593Smuzhiyun    print(rec2._replace(b=222))
130*4882a593Smuzhiyun    print(rec2._replace(b=222)._my_custom_method())
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun    rec3 = MyRecord3('foo', 'bar', 'baz')
133*4882a593Smuzhiyun    print(rec3)
134*4882a593Smuzhiyun    print(rec3._my_custom_method())
135*4882a593Smuzhiyun    print(rec3._replace(q=222))
136*4882a593Smuzhiyun    print(rec3._replace(q=222)._my_custom_method())
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun   # You can also subclass non-abstract ones...
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun    class MyRecord33(MyRecord3):
141*4882a593Smuzhiyun        def __str__(self):
142*4882a593Smuzhiyun            return '< {0!r}, ..., {0!r} >'.format(self.p, self.r)
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun    rec33 = MyRecord33('foo', 'bar', 'baz')
145*4882a593Smuzhiyun    print(rec33)
146*4882a593Smuzhiyun    print(rec33._my_custom_method())
147*4882a593Smuzhiyun    print(rec33._replace(q=222))
148*4882a593Smuzhiyun    print(rec33._replace(q=222)._my_custom_method())
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun    # ...and even override the magic '_fields' attribute again
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun    class MyRecord345(MyRecord3):
153*4882a593Smuzhiyun        _fields = 'e f g h i j k'
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun    rec345 = MyRecord345(1, 2, 3, 4, 3, 2, 1)
156*4882a593Smuzhiyun    print(rec345)
157*4882a593Smuzhiyun    print(rec345._my_custom_method())
158*4882a593Smuzhiyun    print(rec345._replace(f=222))
159*4882a593Smuzhiyun    print(rec345._replace(f=222)._my_custom_method())
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun    # Mixing-in some other classes is also possible:
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun    class MyMixIn(object):
164*4882a593Smuzhiyun        def method(self):
165*4882a593Smuzhiyun            return "MyMixIn.method() called"
166*4882a593Smuzhiyun        def _my_custom_method(self):
167*4882a593Smuzhiyun            return "MyMixIn._my_custom_method() called"
168*4882a593Smuzhiyun        def count(self, item):
169*4882a593Smuzhiyun            return "MyMixIn.count({0}) called".format(item)
170*4882a593Smuzhiyun        def _asdict(self):  # (cannot override a namedtuple method, see below)
171*4882a593Smuzhiyun            return "MyMixIn._asdict() called"
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun    class MyRecord4(MyRecord33, MyMixIn):  # mix-in on the right
174*4882a593Smuzhiyun        _fields = 'j k l x'
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun    class MyRecord5(MyMixIn, MyRecord33):  # mix-in on the left
177*4882a593Smuzhiyun        _fields = 'j k l x y'
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun    rec4 = MyRecord4(1, 2, 3, 2)
180*4882a593Smuzhiyun    print(rec4)
181*4882a593Smuzhiyun    print(rec4.method())
182*4882a593Smuzhiyun    print(rec4._my_custom_method())  # MyRecord33's
183*4882a593Smuzhiyun    print(rec4.count(2))  # tuple's
184*4882a593Smuzhiyun    print(rec4._replace(k=222))
185*4882a593Smuzhiyun    print(rec4._replace(k=222).method())
186*4882a593Smuzhiyun    print(rec4._replace(k=222)._my_custom_method())  # MyRecord33's
187*4882a593Smuzhiyun    print(rec4._replace(k=222).count(8))  # tuple's
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun    rec5 = MyRecord5(1, 2, 3, 2, 1)
190*4882a593Smuzhiyun    print(rec5)
191*4882a593Smuzhiyun    print(rec5.method())
192*4882a593Smuzhiyun    print(rec5._my_custom_method())  # MyMixIn's
193*4882a593Smuzhiyun    print(rec5.count(2))  # MyMixIn's
194*4882a593Smuzhiyun    print(rec5._replace(k=222))
195*4882a593Smuzhiyun    print(rec5._replace(k=222).method())
196*4882a593Smuzhiyun    print(rec5._replace(k=222)._my_custom_method())  # MyMixIn's
197*4882a593Smuzhiyun    print(rec5._replace(k=222).count(2))  # MyMixIn's
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun    # Note that behavior: the standard namedtuple methods cannot be
200*4882a593Smuzhiyun    # overridden by a foreign mix-in -- even if the mix-in is declared
201*4882a593Smuzhiyun    # as the leftmost base class (but, obviously, you can override them
202*4882a593Smuzhiyun    # in the defined class or its subclasses):
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun    print(rec4._asdict())  # (returns a dict, not "MyMixIn._asdict() called")
205*4882a593Smuzhiyun    print(rec5._asdict())  # (returns a dict, not "MyMixIn._asdict() called")
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun    class MyRecord6(MyRecord33):
208*4882a593Smuzhiyun        _fields = 'j k l x y z'
209*4882a593Smuzhiyun        def _asdict(self):
210*4882a593Smuzhiyun            return "MyRecord6._asdict() called"
211*4882a593Smuzhiyun    rec6 = MyRecord6(1, 2, 3, 1, 2, 3)
212*4882a593Smuzhiyun    print(rec6._asdict())  # (this returns "MyRecord6._asdict() called")
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun    # All that record classes are real subclasses of namedtuple.abc:
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun    assert issubclass(MyRecord, namedtuple.abc)
217*4882a593Smuzhiyun    assert issubclass(MyAbstractRecord, namedtuple.abc)
218*4882a593Smuzhiyun    assert issubclass(AnotherAbstractRecord, namedtuple.abc)
219*4882a593Smuzhiyun    assert issubclass(MyRecord2, namedtuple.abc)
220*4882a593Smuzhiyun    assert issubclass(MyRecord3, namedtuple.abc)
221*4882a593Smuzhiyun    assert issubclass(MyRecord33, namedtuple.abc)
222*4882a593Smuzhiyun    assert issubclass(MyRecord345, namedtuple.abc)
223*4882a593Smuzhiyun    assert issubclass(MyRecord4, namedtuple.abc)
224*4882a593Smuzhiyun    assert issubclass(MyRecord5, namedtuple.abc)
225*4882a593Smuzhiyun    assert issubclass(MyRecord6, namedtuple.abc)
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun    # ...but abstract ones are not subclasses of tuple
228*4882a593Smuzhiyun    # (and this is what you probably want):
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun    assert not issubclass(MyAbstractRecord, tuple)
231*4882a593Smuzhiyun    assert not issubclass(AnotherAbstractRecord, tuple)
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun    assert issubclass(MyRecord, tuple)
234*4882a593Smuzhiyun    assert issubclass(MyRecord2, tuple)
235*4882a593Smuzhiyun    assert issubclass(MyRecord3, tuple)
236*4882a593Smuzhiyun    assert issubclass(MyRecord33, tuple)
237*4882a593Smuzhiyun    assert issubclass(MyRecord345, tuple)
238*4882a593Smuzhiyun    assert issubclass(MyRecord4, tuple)
239*4882a593Smuzhiyun    assert issubclass(MyRecord5, tuple)
240*4882a593Smuzhiyun    assert issubclass(MyRecord6, tuple)
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun    # Named tuple classes created with namedtuple() factory function
243*4882a593Smuzhiyun    # (in the "traditional" way) are registered as "virtual" subclasses
244*4882a593Smuzhiyun    # of namedtuple.abc:
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun    MyTuple = namedtuple('MyTuple', 'a b c')
247*4882a593Smuzhiyun    mt = MyTuple(1, 2, 3)
248*4882a593Smuzhiyun    assert issubclass(MyTuple, namedtuple.abc)
249*4882a593Smuzhiyun    assert isinstance(mt, namedtuple.abc)
250