from __future__ import annotations
import itertools
from typing import Dict, Generic, ItemsView, Iterable, Iterator, List, MutableMapping, MutableSequence, Tuple, TypeVar, \
ValuesView
import aiowamp
from .uri import MATCH_PREFIX, MATCH_WILDCARD, URI
__all__ = ["URIMap",
"URIMapValuesView", "URIMapItemsView"]
Rank = Tuple[int, ...]
def rank_prefix(u: aiowamp.URI) -> Rank:
comps = u.count(".") + 1
last_comp_len = len(u.rsplit(".", 1)[0])
# match most components first,
# break ties using length of final comp
return comps, last_comp_len
def rank_wildcard(u: aiowamp.URI) -> Rank:
comp_bitmap = map(lambda c: 2 * bool(c) - 1, u.split("."))
return tuple(sum(g) for _, g in itertools.groupby(comp_bitmap))
KV_co = TypeVar("KV_co", covariant=True)
ValueTuple = Tuple[Rank, "aiowamp.URI", KV_co]
def iter_values_keys(e: Iterable[ValueTuple]) -> Iterator["aiowamp.URI"]:
for _, uri, _ in e:
yield uri
def iter_values_values(e: Iterable[ValueTuple]) -> Iterator[KV_co]:
for _, _, v in e:
yield v
Item = Tuple["aiowamp.URI", KV_co]
def iter_values_items(e: Iterable[ValueTuple]) -> Iterator[Item]:
for _, k, v in e:
yield k, v
def sort_values(e: List[ValueTuple]) -> None:
e.sort(key=lambda v: v[0], reverse=True)
def insort_values_value(e: List[ValueTuple], v: ValueTuple) -> None:
e.append(v)
sort_values(e)
def remove_values_key(e: MutableSequence[ValueTuple], uri: aiowamp.URI) -> None:
for i, (_, u, _) in enumerate(e):
if u == uri:
break
else:
raise KeyError(uri)
del e[i]
[docs]class URIMap(MutableMapping["aiowamp.URI", KV_co], Generic[KV_co]):
__slots__ = ("_exact", "_prefix", "_wildcard")
_exact: Dict[str, KV_co]
_prefix: List[ValueTuple]
_wildcard: List[ValueTuple]
[docs] def __init__(self) -> None:
self._exact = {}
self._prefix = []
self._wildcard = []
def __getitem__(self, uri: str) -> KV_co:
uri = URI.cast(uri)
try:
return self._exact[uri]
except KeyError:
pass
for _, prefix, val in self._prefix:
if URI.prefix_match(uri, prefix):
return val
for _, wildcard, val in self._wildcard:
if URI.wildcard_match(uri, wildcard):
return val
raise KeyError(uri)
def __setitem__(self, uri: aiowamp.URI, value: KV_co) -> None:
if not isinstance(uri, URI):
raise TypeError(f"instance of aiowamp.URI required, not {type(uri).__name__}")
match_policy = uri.match_policy
if match_policy is None:
self._exact[uri] = value
elif match_policy == MATCH_PREFIX:
rank = rank_prefix(uri)
insort_values_value(self._prefix, (rank, uri, value))
elif match_policy == MATCH_WILDCARD:
rank = rank_wildcard(uri)
insort_values_value(self._wildcard, (rank, uri, value))
else:
raise ValueError(f"unknown match policy: {match_policy!r}")
def __delitem__(self, uri: aiowamp.URI) -> None:
if not isinstance(uri, URI):
raise TypeError(f"instance of aiowamp.URI required, not {type(uri).__name__}")
match_policy = uri.match_policy
if match_policy is None:
del self._exact[uri]
elif match_policy == MATCH_PREFIX:
remove_values_key(self._prefix, uri)
elif match_policy == MATCH_WILDCARD:
remove_values_key(self._wildcard, uri)
else:
raise ValueError(f"unknown match policy: {match_policy!r}")
def __len__(self) -> int:
return len(self._exact) + len(self._prefix) + len(self._wildcard)
def __iter__(self):
yield from self._exact
yield from iter_values_keys(self._prefix)
yield from iter_values_keys(self._wildcard)
[docs] def values(self) -> URIMapValuesView[KV_co]:
return URIMapValuesView(self)
[docs] def items(self) -> URIMapItemsView[KV_co]:
return URIMapItemsView(self)
[docs]class URIMapValuesView(ValuesView[KV_co], Generic[KV_co]):
__slots__ = ()
_mapping: URIMap[KV_co]
[docs] def __init__(self, mapping: URIMap[KV_co]) -> None:
self._mapping = mapping
def __contains__(self, item: KV_co) -> bool:
mapping = self._mapping
return item in mapping._exact.values() or \
item in iter_values_values(mapping._prefix) or \
item in iter_values_values(mapping._wildcard)
def __iter__(self) -> Iterator[KV_co]:
mapping = self._mapping
yield from mapping._exact.values()
yield from iter_values_values(mapping._prefix)
yield from iter_values_values(mapping._wildcard)
[docs]class URIMapItemsView(ItemsView["aiowamp.URI", KV_co], Generic[KV_co]):
__slots__ = ()
_mapping: URIMap[KV_co]
[docs] def __init__(self, mapping: URIMap[KV_co]) -> None:
self._mapping = mapping
def __contains__(self, item: Item) -> bool:
key, value = item
try:
actual = self._mapping[key]
except LookupError:
return False
return actual is value or actual == value
def __iter__(self) -> Iterator[Iterator]:
mapping = self._mapping
yield from mapping._exact.items()
yield from iter_values_items(mapping._prefix)
yield from iter_values_items(mapping._wildcard)