test: use built-in collection types for type hints (Python 3.9 / PEP 585)

Since Python 3.9, type hinting has become a little less awkward, as for
collection types one doesn't need to import the corresponding
capitalized types (`Dict`, `List`, `Set`, `Tuple`, ...) anymore, but can
use the built-in types directly. [1] [2]
This commit applies the replacement for all Python scripts (i.e. in the
contrib and test folders) for the basic types:
    - typing.Dict  -> dict
    - typing.List  -> list
    - typing.Set   -> set
    - typing.Tuple -> tuple

[1] https://docs.python.org/3.9/whatsnew/3.9.html#type-hinting-generics-in-standard-collections
[2] https://peps.python.org/pep-0585/#implementation for a list of type
This commit is contained in:
Sebastian Falbesoner
2023-10-25 00:55:17 +02:00
parent d53400e75e
commit d516cf83ed
18 changed files with 76 additions and 88 deletions

View File

@@ -12,9 +12,9 @@ import random
import unittest
from enum import Enum
from functools import total_ordering
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union, overload
from typing import Callable, Iterable, Optional, Union, overload
def net_to_prefix(net: Union[ipaddress.IPv4Network,ipaddress.IPv6Network]) -> List[bool]:
def net_to_prefix(net: Union[ipaddress.IPv4Network,ipaddress.IPv6Network]) -> list[bool]:
"""
Convert an IPv4 or IPv6 network to a prefix represented as a list of bits.
@@ -32,7 +32,7 @@ def net_to_prefix(net: Union[ipaddress.IPv4Network,ipaddress.IPv6Network]) -> Li
assert (netrange & ((1 << (128 - num_bits)) - 1)) == 0
return [((netrange >> (127 - i)) & 1) != 0 for i in range(num_bits)]
def prefix_to_net(prefix: List[bool]) -> Union[ipaddress.IPv4Network,ipaddress.IPv6Network]:
def prefix_to_net(prefix: list[bool]) -> Union[ipaddress.IPv4Network,ipaddress.IPv6Network]:
"""The reverse operation of net_to_prefix."""
# Convert to number
netrange = sum(b << (127 - i) for i, b in enumerate(prefix))
@@ -47,10 +47,10 @@ def prefix_to_net(prefix: List[bool]) -> Union[ipaddress.IPv4Network,ipaddress.I
return ipaddress.IPv6Network((netrange, num_bits), True)
# Shortcut for (prefix, ASN) entries.
ASNEntry = Tuple[List[bool], int]
ASNEntry = tuple[list[bool], int]
# Shortcut for (prefix, old ASN, new ASN) entries.
ASNDiff = Tuple[List[bool], int, int]
ASNDiff = tuple[list[bool], int, int]
class _VarLenCoder:
"""
@@ -75,7 +75,7 @@ class _VarLenCoder:
other classes start one past the last element of the class before it.
"""
def __init__(self, minval: int, clsbits: List[int]):
def __init__(self, minval: int, clsbits: list[int]):
"""Construct a new _VarLenCoder."""
self._minval = minval
self._clsbits = clsbits
@@ -85,7 +85,7 @@ class _VarLenCoder:
"""Check whether value val is in the range this coder supports."""
return self._minval <= val <= self._maxval
def encode(self, val: int, ret: List[int]) -> None:
def encode(self, val: int, ret: list[int]) -> None:
"""Append encoding of val onto integer list ret."""
assert self._minval <= val <= self._maxval
@@ -120,7 +120,7 @@ class _VarLenCoder:
break
return ret + bits
def decode(self, stream, bitpos) -> Tuple[int,int]:
def decode(self, stream, bitpos) -> tuple[int,int]:
"""Decode a number starting at bitpos in stream, returning value and new bitpos."""
val = self._minval
bits = 0
@@ -281,11 +281,11 @@ class ASMap:
- mappings, represented by new trie nodes.
"""
def update(self, prefix: List[bool], asn: int) -> None:
def update(self, prefix: list[bool], asn: int) -> None:
"""Update this ASMap object to map prefix to the specified asn."""
assert asn == 0 or _CODER_ASN.can_encode(asn)
def recurse(node: List, offset: int) -> None:
def recurse(node: list, offset: int) -> None:
if offset == len(prefix):
# Reached the end of prefix; overwrite this node.
node.clear()
@@ -306,7 +306,7 @@ class ASMap:
node.append(oldasn)
recurse(self._trie, 0)
def update_multi(self, entries: List[Tuple[List[bool], int]]) -> None:
def update_multi(self, entries: list[tuple[list[bool], int]]) -> None:
"""Apply multiple update operations, where longer prefixes take precedence."""
entries.sort(key=lambda entry: len(entry[0]))
for prefix, asn in entries:
@@ -314,7 +314,7 @@ class ASMap:
def _set_trie(self, trie) -> None:
"""Set trie directly. Internal use only."""
def recurse(node: List) -> None:
def recurse(node: list) -> None:
if len(node) < 2:
return
recurse(node[0])
@@ -342,7 +342,7 @@ class ASMap:
for prefix, asn in sorted(entries, key=entry_key):
self.update(prefix, asn)
def lookup(self, prefix: List[bool]) -> Optional[int]:
def lookup(self, prefix: list[bool]) -> Optional[int]:
"""Look up a prefix. Returns ASN, or 0 if unassigned, or None if indeterminate."""
node = self._trie
for bit in prefix:
@@ -353,11 +353,11 @@ class ASMap:
return node[0]
return None
def _to_entries_flat(self, fill: bool = False) -> List[ASNEntry]:
def _to_entries_flat(self, fill: bool = False) -> list[ASNEntry]:
"""Convert an ASMap object to a list of non-overlapping (prefix, asn) objects."""
prefix : List[bool] = []
prefix : list[bool] = []
def recurse(node: List) -> List[ASNEntry]:
def recurse(node: list) -> list[ASNEntry]:
ret = []
if len(node) == 1:
if node[0] > 0:
@@ -375,24 +375,24 @@ class ASMap:
return ret
return recurse(self._trie)
def _to_entries_minimal(self, fill: bool = False) -> List[ASNEntry]:
def _to_entries_minimal(self, fill: bool = False) -> list[ASNEntry]:
"""Convert a trie to a minimal list of ASNEntry objects, exploiting overlap."""
prefix : List[bool] = []
prefix : list[bool] = []
def recurse(node: List) -> (Tuple[Dict[Optional[int], List[ASNEntry]], bool]):
def recurse(node: list) -> (tuple[dict[Optional[int], list[ASNEntry]], bool]):
if len(node) == 1 and node[0] == 0:
return {None if fill else 0: []}, True
if len(node) == 1:
return {node[0]: [], None: [(list(prefix), node[0])]}, False
ret: Dict[Optional[int], List[ASNEntry]] = {}
ret: dict[Optional[int], list[ASNEntry]] = {}
prefix.append(False)
left, lhole = recurse(node[0])
prefix[-1] = True
right, rhole = recurse(node[1])
prefix.pop()
hole = not fill and (lhole or rhole)
def candidate(ctx: Optional[int], res0: Optional[List[ASNEntry]],
res1: Optional[List[ASNEntry]]):
def candidate(ctx: Optional[int], res0: Optional[list[ASNEntry]],
res1: Optional[list[ASNEntry]]):
if res0 is not None and res1 is not None:
if ctx not in ret or len(res0) + len(res1) < len(ret[ctx]):
ret[ctx] = res0 + res1
@@ -417,7 +417,7 @@ class ASMap:
"""Convert this ASMap object to a string containing Python code constructing it."""
return f"ASMap({self._trie})"
def to_entries(self, overlapping: bool = True, fill: bool = False) -> List[ASNEntry]:
def to_entries(self, overlapping: bool = True, fill: bool = False) -> list[ASNEntry]:
"""
Convert the mappings in this ASMap object to a list of ASNEntry objects.
@@ -448,7 +448,7 @@ class ASMap:
assert max_asn >= 1 or unassigned_prob == 1
assert _CODER_ASN.can_encode(max_asn)
assert 0.0 <= unassigned_prob <= 1.0
trie: List = []
trie: list = []
leaves = [trie]
ret = ASMap()
for i in range(1, num_leaves):
@@ -472,12 +472,12 @@ class ASMap:
def _to_binnode(self, fill: bool = False) -> _BinNode:
"""Convert a trie to a _BinNode object."""
def recurse(node: List) -> Tuple[Dict[Optional[int], _BinNode], bool]:
def recurse(node: list) -> tuple[dict[Optional[int], _BinNode], bool]:
if len(node) == 1 and node[0] == 0:
return {(None if fill else 0): _BinNode.make_end()}, True
if len(node) == 1:
return {None: _BinNode.make_leaf(node[0]), node[0]: _BinNode.make_end()}, False
ret: Dict[Optional[int], _BinNode] = {}
ret: dict[Optional[int], _BinNode] = {}
left, lhole = recurse(node[0])
right, rhole = recurse(node[1])
hole = (lhole or rhole) and not fill
@@ -507,7 +507,7 @@ class ASMap:
@staticmethod
def _from_binnode(binnode: _BinNode) -> "ASMap":
"""Construct an ASMap object from a _BinNode. Internal use only."""
def recurse(node: _BinNode, default: int) -> List:
def recurse(node: _BinNode, default: int) -> list:
if node.ins == _Instruction.RETURN:
return [node.arg1]
if node.ins == _Instruction.JUMP:
@@ -542,7 +542,7 @@ class ASMap:
Returns:
A bytes object with the encoding of this ASMap object.
"""
bits: List[int] = []
bits: list[int] = []
def recurse(node: _BinNode) -> None:
_CODER_INS.encode(node.ins.value, bits)
@@ -582,11 +582,11 @@ class ASMap:
def from_binary(bindata: bytes) -> Optional["ASMap"]:
"""Decode an ASMap object from the provided binary encoding."""
bits: List[int] = []
bits: list[int] = []
for byte in bindata:
bits.extend((byte >> i) & 1 for i in range(8))
def recurse(bitpos: int) -> Tuple[_BinNode, int]:
def recurse(bitpos: int) -> tuple[_BinNode, int]:
insval, bitpos = _CODER_INS.decode(bits, bitpos)
ins = _Instruction(insval)
if ins == _Instruction.RETURN:
@@ -632,7 +632,7 @@ class ASMap:
def extends(self, req: "ASMap") -> bool:
"""Determine whether this matches req for all subranges where req is assigned."""
def recurse(actual: List, require: List) -> bool:
def recurse(actual: list, require: list) -> bool:
if len(require) == 1 and require[0] == 0:
return True
if len(require) == 1:
@@ -646,20 +646,20 @@ class ASMap:
#pylint: disable=protected-access
return recurse(self._trie, req._trie)
def diff(self, other: "ASMap") -> List[ASNDiff]:
def diff(self, other: "ASMap") -> list[ASNDiff]:
"""Compute the diff from self to other."""
prefix: List[bool] = []
ret: List[ASNDiff] = []
prefix: list[bool] = []
ret: list[ASNDiff] = []
def recurse(old_node: List, new_node: List):
def recurse(old_node: list, new_node: list):
if len(old_node) == 1 and len(new_node) == 1:
if old_node[0] != new_node[0]:
ret.append((list(prefix), old_node[0], new_node[0]))
else:
old_left: List = old_node if len(old_node) == 1 else old_node[0]
old_right: List = old_node if len(old_node) == 1 else old_node[1]
new_left: List = new_node if len(new_node) == 1 else new_node[0]
new_right: List = new_node if len(new_node) == 1 else new_node[1]
old_left: list = old_node if len(old_node) == 1 else old_node[0]
old_right: list = old_node if len(old_node) == 1 else old_node[1]
new_left: list = new_node if len(new_node) == 1 else new_node[0]
new_right: list = new_node if len(new_node) == 1 else new_node[1]
prefix.append(False)
recurse(old_left, new_left)
prefix[-1] = True
@@ -760,7 +760,7 @@ class TestASMap(unittest.TestCase):
# It starts off being equal to asmap.
patched = copy.copy(asmap)
# Keep a list of patches performed.
patches: List[ASNEntry] = []
patches: list[ASNEntry] = []
# Initially there cannot be any difference.
self.assertEqual(asmap.diff(patched), [])
# Make 5 patches, each building on top of the previous ones.

View File

@@ -11,7 +11,7 @@ import collections
import ipaddress
import re
import sys
from typing import List, Dict, Union
from typing import Union
from asmap import ASMap, net_to_prefix
@@ -117,14 +117,14 @@ def parseline(line: str) -> Union[dict, None]:
'sortkey': sortkey,
}
def dedup(ips: List[Dict]) -> List[Dict]:
def dedup(ips: list[dict]) -> list[dict]:
""" Remove duplicates from `ips` where multiple ips share address and port. """
d = {}
for ip in ips:
d[ip['ip'],ip['port']] = ip
return list(d.values())
def filtermultiport(ips: List[Dict]) -> List[Dict]:
def filtermultiport(ips: list[dict]) -> list[dict]:
""" Filter out hosts with more nodes per IP"""
hist = collections.defaultdict(list)
for ip in ips:
@@ -132,7 +132,7 @@ def filtermultiport(ips: List[Dict]) -> List[Dict]:
return [value[0] for (key,value) in list(hist.items()) if len(value)==1]
# Based on Greg Maxwell's seed_filter.py
def filterbyasn(asmap: ASMap, ips: List[Dict], max_per_asn: Dict, max_per_net: int) -> List[Dict]:
def filterbyasn(asmap: ASMap, ips: list[dict], max_per_asn: dict, max_per_net: int) -> list[dict]:
""" Prunes `ips` by
(a) trimming ips to have at most `max_per_net` ips from each net (e.g. ipv4, ipv6); and
(b) trimming ips to have at most `max_per_asn` ips from each asn in each net.
@@ -143,8 +143,8 @@ def filterbyasn(asmap: ASMap, ips: List[Dict], max_per_asn: Dict, max_per_net: i
# Filter IPv46 by ASN, and limit to max_per_net per network
result = []
net_count: Dict[str, int] = collections.defaultdict(int)
asn_count: Dict[int, int] = collections.defaultdict(int)
net_count: dict[str, int] = collections.defaultdict(int)
asn_count: dict[int, int] = collections.defaultdict(int)
for i, ip in enumerate(ips_ipv46):
if net_count[ip['net']] == max_per_net:
@@ -165,9 +165,9 @@ def filterbyasn(asmap: ASMap, ips: List[Dict], max_per_asn: Dict, max_per_net: i
result.extend(ips_onion[0:max_per_net])
return result
def ip_stats(ips: List[Dict]) -> str:
def ip_stats(ips: list[dict]) -> str:
""" Format and return pretty string from `ips`. """
hist: Dict[str, int] = collections.defaultdict(int)
hist: dict[str, int] = collections.defaultdict(int)
for ip in ips:
if ip is not None:
hist[ip['net']] += 1