|
3 | 3 | from . import exceptions
|
4 | 4 | from .codecs import codec_by_name
|
5 | 5 |
|
| 6 | +__all__ = ("Protocol", "PROTOCOLS", "REGISTRY") |
| 7 | + |
6 | 8 |
|
7 | 9 | # source of protocols https://github.com/multiformats/multicodec/blob/master/table.csv#L382
|
8 | 10 | # replicating table here to:
|
@@ -88,7 +90,7 @@ def __repr__(self):
|
88 | 90 | )
|
89 | 91 |
|
90 | 92 |
|
91 |
| -# Protocols is the list of multiaddr protocols supported by this module. |
| 93 | +# List of multiaddr protocols supported by this module by default |
92 | 94 | PROTOCOLS = [
|
93 | 95 | Protocol(P_IP4, 'ip4', 'ip4'),
|
94 | 96 | Protocol(P_TCP, 'tcp', 'uint16be'),
|
@@ -118,56 +120,180 @@ def __repr__(self):
|
118 | 120 | Protocol(P_UNIX, 'unix', 'fspath'),
|
119 | 121 | ]
|
120 | 122 |
|
121 |
| -_names_to_protocols = {proto.name: proto for proto in PROTOCOLS} |
122 |
| -_codes_to_protocols = {proto.code: proto for proto in PROTOCOLS} |
123 | 123 |
|
| 124 | +class ProtocolRegistry: |
| 125 | + """A collection of individual Multiaddr protocols indexed for fast lookup""" |
| 126 | + __slots__ = ("_codes_to_protocols", "_locked", "_names_to_protocols") |
| 127 | + |
| 128 | + def __init__(self, protocols=()): |
| 129 | + self._locked = False |
| 130 | + self._codes_to_protocols = {proto.code: proto for proto in protocols} |
| 131 | + self._names_to_protocols = {proto.name: proto for proto in protocols} |
| 132 | + |
| 133 | + def add(self, proto): |
| 134 | + """Add the given protocol description to this registry |
| 135 | +
|
| 136 | + Raises |
| 137 | + ------ |
| 138 | + ~multiaddr.exceptions.ProtocolRegistryLocked |
| 139 | + Protocol registry is locked and does not accept any new entries. |
| 140 | +
|
| 141 | + You can use `.copy(unlock=True)` to copy an existing locked registry |
| 142 | + and unlock it. |
| 143 | + ~multiaddr.exceptions.ProtocolExistsError |
| 144 | + A protocol with the given name or code already exists. |
| 145 | + """ |
| 146 | + if self._locked: |
| 147 | + raise exceptions.ProtocolRegistryLocked() |
| 148 | + |
| 149 | + if proto.name in self._names_to_protocols: |
| 150 | + raise exceptions.ProtocolExistsError(proto, "name") |
| 151 | + |
| 152 | + if proto.code in self._codes_to_protocols: |
| 153 | + raise exceptions.ProtocolExistsError(proto, "code") |
| 154 | + |
| 155 | + self._names_to_protocols[proto.name] = proto |
| 156 | + self._codes_to_protocols[proto.code] = proto |
| 157 | + return proto |
| 158 | + |
| 159 | + def add_alias_name(self, proto, alias_name): |
| 160 | + """Add an alternate name for an existing protocol description to the registry |
| 161 | +
|
| 162 | + Raises |
| 163 | + ------ |
| 164 | + ~multiaddr.exceptions.ProtocolRegistryLocked |
| 165 | + Protocol registry is locked and does not accept any new entries. |
| 166 | +
|
| 167 | + You can use `.copy(unlock=True)` to copy an existing locked registry |
| 168 | + and unlock it. |
| 169 | + ~multiaddr.exceptions.ProtocolExistsError |
| 170 | + A protocol with the given name already exists. |
| 171 | + ~multiaddr.exceptions.ProtocolNotFoundError |
| 172 | + No protocol matching *proto* could be found. |
| 173 | + """ |
| 174 | + if self._locked: |
| 175 | + raise exceptions.ProtocolRegistryLocked() |
| 176 | + |
| 177 | + proto = self.find(proto) |
| 178 | + assert self._names_to_protocols.get(proto.name) is proto, \ |
| 179 | + "Protocol to alias must have already been added to the registry" |
| 180 | + |
| 181 | + if alias_name in self._names_to_protocols: |
| 182 | + raise exceptions.ProtocolExistsError(self._names_to_protocols[alias_name], "name") |
| 183 | + |
| 184 | + self._names_to_protocols[alias_name] = proto |
| 185 | + |
| 186 | + def add_alias_code(self, proto, alias_code): |
| 187 | + """Add an alternate code for an existing protocol description to the registry |
| 188 | +
|
| 189 | + Raises |
| 190 | + ------ |
| 191 | + ~multiaddr.exceptions.ProtocolRegistryLocked |
| 192 | + Protocol registry is locked and does not accept any new entries. |
124 | 193 |
|
125 |
| -def add_protocol(proto): |
126 |
| - if proto.name in _names_to_protocols: |
127 |
| - raise exceptions.ProtocolExistsError(proto, "name") |
| 194 | + You can use `.copy(unlock=True)` to copy an existing locked registry |
| 195 | + and unlock it. |
| 196 | + ~multiaddr.exceptions.ProtocolExistsError |
| 197 | + A protocol with the given code already exists. |
| 198 | + ~multiaddr.exceptions.ProtocolNotFoundError |
| 199 | + No protocol matching *proto* could be found. |
| 200 | + """ |
| 201 | + if self._locked: |
| 202 | + raise exceptions.ProtocolRegistryLocked() |
128 | 203 |
|
129 |
| - if proto.code in _codes_to_protocols: |
130 |
| - raise exceptions.ProtocolExistsError(proto, "code") |
| 204 | + proto = self.find(proto) |
| 205 | + assert self._codes_to_protocols.get(proto.code) is proto, \ |
| 206 | + "Protocol to alias must have already been added to the registry" |
131 | 207 |
|
132 |
| - PROTOCOLS.append(proto) |
133 |
| - _names_to_protocols[proto.name] = proto |
134 |
| - _codes_to_protocols[proto.code] = proto |
135 |
| - return None |
| 208 | + if alias_code in self._codes_to_protocols: |
| 209 | + raise exceptions.ProtocolExistsError(self._codes_to_protocols[alias_code], "name") |
| 210 | + |
| 211 | + self._codes_to_protocols[alias_code] = proto |
| 212 | + |
| 213 | + def lock(self): |
| 214 | + """Lock this registry instance to deny any further changes""" |
| 215 | + self._locked = True |
| 216 | + |
| 217 | + @property |
| 218 | + def locked(self): |
| 219 | + return self._locked |
| 220 | + |
| 221 | + def copy(self, *, unlock=False): |
| 222 | + """Create a copy of this protocol registry |
| 223 | +
|
| 224 | + Arguments |
| 225 | + --------- |
| 226 | + unlock |
| 227 | + Create the copied registry unlocked even if the current one is locked? |
| 228 | + """ |
| 229 | + registry = ProtocolRegistry() |
| 230 | + registry._locked = self._locked and not unlock |
| 231 | + registry._codes_to_protocols = self._codes_to_protocols.copy() |
| 232 | + registry._names_to_protocols = self._names_to_protocols.copy() |
| 233 | + return registry |
| 234 | + |
| 235 | + __copy__ = copy |
| 236 | + |
| 237 | + def find_by_name(self, name): |
| 238 | + """Look up a protocol by its human-readable name |
| 239 | +
|
| 240 | + Raises |
| 241 | + ------ |
| 242 | + ~multiaddr.exceptions.ProtocolNotFoundError |
| 243 | + """ |
| 244 | + if name not in self._names_to_protocols: |
| 245 | + raise exceptions.ProtocolNotFoundError(name, "name") |
| 246 | + return self._names_to_protocols[name] |
| 247 | + |
| 248 | + def find_by_code(self, code): |
| 249 | + """Look up a protocol by its binary representation code |
| 250 | +
|
| 251 | + Raises |
| 252 | + ------ |
| 253 | + ~multiaddr.exceptions.ProtocolNotFoundError |
| 254 | + """ |
| 255 | + if code not in self._codes_to_protocols: |
| 256 | + raise exceptions.ProtocolNotFoundError(code, "code") |
| 257 | + return self._codes_to_protocols[code] |
| 258 | + |
| 259 | + def find(self, proto): |
| 260 | + """Look up a protocol by its name or code, return existing protocol objects unchanged |
| 261 | +
|
| 262 | + Raises |
| 263 | + ------ |
| 264 | + ~multiaddr.exceptions.ProtocolNotFoundError |
| 265 | + """ |
| 266 | + if isinstance(proto, Protocol): |
| 267 | + return proto |
| 268 | + elif isinstance(proto, str): |
| 269 | + return self.find_by_name(proto) |
| 270 | + elif isinstance(proto, int): |
| 271 | + return self.find_by_code(proto) |
| 272 | + else: |
| 273 | + raise TypeError("Protocol object, name or code expected, got {0!r}".format(proto)) |
| 274 | + |
| 275 | + |
| 276 | +REGISTRY = ProtocolRegistry(PROTOCOLS) |
| 277 | +REGISTRY.add_alias_name("p2p", "ipfs") |
| 278 | +REGISTRY.lock() |
136 | 279 |
|
137 | 280 |
|
138 | 281 | def protocol_with_name(name):
|
139 |
| - if name not in _names_to_protocols: |
140 |
| - raise exceptions.ProtocolNotFoundError(name, "name") |
141 |
| - return _names_to_protocols[name] |
| 282 | + return REGISTRY.find_by_name(name) |
142 | 283 |
|
143 | 284 |
|
144 | 285 | def protocol_with_code(code):
|
145 |
| - if code not in _codes_to_protocols: |
146 |
| - raise exceptions.ProtocolNotFoundError(code, "code") |
147 |
| - return _codes_to_protocols[code] |
| 286 | + return REGISTRY.find_by_code(code) |
148 | 287 |
|
149 | 288 |
|
150 | 289 | def protocol_with_any(proto):
|
151 |
| - if isinstance(proto, Protocol): |
152 |
| - return proto |
153 |
| - elif isinstance(proto, int): |
154 |
| - return protocol_with_code(proto) |
155 |
| - elif isinstance(proto, str): |
156 |
| - return protocol_with_name(proto) |
157 |
| - else: |
158 |
| - raise TypeError("Protocol object, name or code expected, got {0!r}".format(proto)) |
| 290 | + return REGISTRY.find(proto) |
159 | 291 |
|
160 | 292 |
|
161 | 293 | def protocols_with_string(string):
|
162 | 294 | """Return a list of protocols matching given string."""
|
163 |
| - # Normalize string |
164 |
| - while "//" in string: |
165 |
| - string = string.replace("//", "/") |
166 |
| - string = string.strip("/") |
167 |
| - if not string: |
168 |
| - return [] |
169 |
| - |
170 | 295 | ret = []
|
171 | 296 | for name in string.split("/"):
|
172 |
| - ret.append(protocol_with_name(name)) |
| 297 | + if len(name) > 0: |
| 298 | + ret.append(protocol_with_name(name)) |
173 | 299 | return ret
|
0 commit comments