1
1
# -*- coding: utf-8 -*-
2
- from copy import copy
2
+ try :
3
+ import collections .abc
4
+ except ImportError : # pragma: no cover (PY2)
5
+ import collections
6
+ collections .abc = collections
3
7
4
8
import six
5
9
10
14
from .transforms import bytes_to_string
11
15
12
16
13
- class Multiaddr (object ):
17
+ __all__ = ("Multiaddr" ,)
18
+
19
+
20
+
21
+ class MultiAddrKeys (collections .abc .KeysView , collections .abc .Sequence ):
22
+ def __contains__ (self , proto ):
23
+ proto = protocols .protocol_with_any (proto )
24
+ return collections .abc .Sequence .__contains__ (self , proto )
25
+
26
+ def __getitem__ (self , idx ):
27
+ if idx < 0 :
28
+ idx = len (self )+ idx
29
+ for idx2 , proto in enumerate (self ):
30
+ if idx2 == idx :
31
+ return proto
32
+ raise IndexError ("Protocol list index out of range" )
33
+
34
+ __hash__ = collections .abc .KeysView ._hash
35
+
36
+ def __iter__ (self ):
37
+ for proto , _ , _ in bytes_iter (self ._mapping .to_bytes ()):
38
+ yield proto
39
+
40
+
41
+ class MultiAddrItems (collections .abc .ItemsView , collections .abc .Sequence ):
42
+ def __contains__ (self , item ):
43
+ proto , value = item
44
+ proto = protocols .protocol_with_any (proto )
45
+ return collections .abc .Sequence .__contains__ (self , (proto , value ))
46
+
47
+ def __getitem__ (self , idx ):
48
+ if idx < 0 :
49
+ idx = len (self )+ idx
50
+ for idx2 , item in enumerate (self ):
51
+ if idx2 == idx :
52
+ return item
53
+ raise IndexError ("Protocol item list index out of range" )
54
+
55
+ def __iter__ (self ):
56
+ for proto , codec , part in bytes_iter (self ._mapping .to_bytes ()):
57
+ if codec .SIZE != 0 :
58
+ try :
59
+ # If we have an address, return it
60
+ yield proto , codec .to_string (proto , part )
61
+ except Exception as exc :
62
+ six .raise_from (exceptions .BinaryParseError (str (exc ), self ._mapping .to_bytes (), proto .name , exc ), exc )
63
+ else :
64
+ # We were given something like '/utp', which doesn't have
65
+ # an address, so return None
66
+ yield proto , None
67
+
68
+
69
+ class MultiAddrValues (collections .abc .ValuesView , collections .abc .Sequence ):
70
+ __contains__ = collections .abc .Sequence .__contains__
71
+
72
+ def __getitem__ (self , idx ):
73
+ if idx < 0 :
74
+ idx = len (self )+ idx
75
+ for idx2 , proto in enumerate (self ):
76
+ if idx2 == idx :
77
+ return proto
78
+ raise IndexError ("Protocol value list index out of range" )
79
+
80
+ def __iter__ (self ):
81
+ for _ , value in MultiAddrItems (self ._mapping ):
82
+ yield value
83
+
84
+
85
+
86
+ class Multiaddr (collections .abc .Mapping ):
14
87
"""Multiaddr is a representation of multiple nested internet addresses.
15
88
16
89
Multiaddr is a cross-protocol, cross-platform format for representing
@@ -39,34 +112,42 @@ def __init__(self, addr):
39
112
# On Python 2 text string will often be binary anyways so detect the
40
113
# obvious case of a “binary-encoded” multiaddr starting with a slash
41
114
# and decode it into text
42
- if six .PY2 and isinstance (addr , str ) and addr .startswith ("/" ):
115
+ if six .PY2 and isinstance (addr , str ) and addr .startswith ("/" ): # pragma: no cover (PY2)
43
116
addr = addr .decode ("utf-8" )
44
117
45
118
if isinstance (addr , six .text_type ):
46
119
self ._bytes = string_to_bytes (addr )
47
120
elif isinstance (addr , six .binary_type ):
48
121
self ._bytes = addr
122
+ elif isinstance (addr , Multiaddr ):
123
+ self ._bytes = addr .to_bytes ()
49
124
else :
50
- raise TypeError ("MultiAddr must be bytes or str " )
125
+ raise TypeError ("MultiAddr must be bytes, str or another MultiAddr instance " )
51
126
52
127
def __eq__ (self , other ):
53
128
"""Checks if two Multiaddr objects are exactly equal."""
54
129
return self ._bytes == other ._bytes
55
130
56
- def __ne__ (self , other ):
57
- return not (self == other )
58
-
59
131
def __str__ (self ):
60
132
"""Return the string representation of this Multiaddr.
61
133
62
134
May raise a :class:`~multiaddr.exceptions.BinaryParseError` if the
63
135
stored MultiAddr binary representation is invalid."""
64
136
return bytes_to_string (self ._bytes )
65
137
138
+ def __contains__ (self , proto ):
139
+ return proto in MultiAddrKeys (self )
140
+
141
+ def __iter__ (self ):
142
+ return iter (MultiAddrKeys (self ))
143
+
144
+ def __len__ (self ):
145
+ return sum ((1 for _ in bytes_iter (self .to_bytes ())))
146
+
66
147
# On Python 2 __str__ needs to return binary text, so expose the original
67
148
# function as __unicode__ and transparently encode its returned text based
68
149
# on the current locale
69
- if six .PY2 :
150
+ if six .PY2 : # pragma: no cover (PY2)
70
151
__unicode__ = __str__
71
152
72
153
def __str__ (self ):
@@ -82,7 +163,15 @@ def to_bytes(self):
82
163
83
164
def protocols (self ):
84
165
"""Returns a list of Protocols this Multiaddr includes."""
85
- return list (proto for proto , _ , _ in bytes_iter (self .to_bytes ()))
166
+ return MultiAddrKeys (self )
167
+
168
+ keys = protocols
169
+
170
+ def items (self ):
171
+ return MultiAddrItems (self )
172
+
173
+ def values (self ):
174
+ return MultiAddrValues (self )
86
175
87
176
def encapsulate (self , other ):
88
177
"""Wrap this Multiaddr around another.
@@ -91,7 +180,7 @@ def encapsulate(self, other):
91
180
/ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80
92
181
"""
93
182
mb = self .to_bytes ()
94
- ob = other .to_bytes ()
183
+ ob = Multiaddr ( other ) .to_bytes ()
95
184
return Multiaddr (b'' .join ([mb , ob ]))
96
185
97
186
def decapsulate (self , other ):
@@ -100,35 +189,35 @@ def decapsulate(self, other):
100
189
For example:
101
190
/ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = /tcp/80
102
191
"""
103
- s1 = str ( self )
104
- s2 = str (other )
192
+ s1 = self . to_bytes ( )
193
+ s2 = Multiaddr (other ). to_bytes ( )
105
194
try :
106
195
idx = s1 .rindex (s2 )
107
196
except ValueError :
108
197
# if multiaddr not contained, returns a copy
109
- return copy (self )
198
+ return Multiaddr (self )
110
199
return Multiaddr (s1 [:idx ])
111
200
112
201
def value_for_protocol (self , proto ):
113
- """Return the value (if any) following the specified protocol."""
114
- if not isinstance (proto , protocols .Protocol ):
115
- if isinstance (proto , int ):
116
- proto = protocols .protocol_with_code (proto )
117
- elif isinstance (proto , six .string_types ):
118
- proto = protocols .protocol_with_name (proto )
119
- else :
120
- raise TypeError ("Protocol object, name or code expected, got {0!r}" .format (proto ))
121
-
122
- for proto2 , codec , part in bytes_iter (self .to_bytes ()):
123
- if proto2 == proto :
124
- if codec .SIZE != 0 :
125
- try :
126
- # If we have an address, return it
127
- return codec .to_string (proto2 , part )
128
- except Exception as exc :
129
- six .raise_from (exceptions .BinaryParseError (str (exc ), self .to_bytes (), proto2 .name , exc ), exc )
130
- else :
131
- # We were given something like '/utp', which doesn't have
132
- # an address, so return ''
133
- return ''
202
+ """Return the value (if any) following the specified protocol
203
+
204
+ Returns
205
+ -------
206
+ union[object, NoneType]
207
+ The parsed protocol value for the given protocol code or ``None``
208
+ if the given protocol does not require any value
209
+
210
+ Raises
211
+ ------
212
+ ~multiaddr.exceptions.BinaryParseError
213
+ The stored MultiAddr binary representation is invalid
214
+ ~multiaddr.exceptions.ProtocolLookupError
215
+ MultiAddr does not contain any instance of this protocol
216
+ """
217
+ proto = protocols .protocol_with_any (proto )
218
+ for proto2 , value in self .items ():
219
+ if proto2 is proto or proto2 == proto :
220
+ return value
134
221
raise exceptions .ProtocolLookupError (proto , str (self ))
222
+
223
+ __getitem__ = value_for_protocol
0 commit comments