Skip to content

Commit be81778

Browse files
committed
feature: reimplement as MultiDict
1 parent 81a79c0 commit be81778

File tree

4 files changed

+373
-215
lines changed

4 files changed

+373
-215
lines changed

alpacloud/eztag/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ python_sources()
22

33
python_tests(
44
name="tests",
5-
# dependencies=["./test_resources:k8s_objs"],
5+
# dependencies=["./test_resources:k8s_objs"],
66
)
77

88
python_test_utils(

alpacloud/eztag/multidict.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Iterable, TypeAlias
2+
3+
K: TypeAlias = str
4+
V: TypeAlias = str | None
5+
6+
7+
def _is_collection(obj):
8+
"""
9+
Checks if an object is an iterable collection, excluding strings and bytes.
10+
"""
11+
return isinstance(obj, Iterable) and not isinstance(obj, (str, bytes, bytearray))
12+
13+
14+
class MultiDict:
15+
"""
16+
A dictionary that allows multiple values for the same key.
17+
This allows us to have a tag set like `env=prd, env=stg`
18+
"""
19+
20+
def __init__(self):
21+
self.d: dict[K, set[V]] = {}
22+
23+
@classmethod
24+
def from_dict(cls, d: dict[K, V]):
25+
md = cls()
26+
for k, v in d.items():
27+
md[k] = {v}
28+
return md
29+
30+
@classmethod
31+
def create(cls, d: dict[K, Iterable[V] | V]):
32+
md = cls()
33+
for k, vs in d.items():
34+
if not _is_collection(vs):
35+
n = {vs}
36+
else:
37+
n = set(vs)
38+
md.d[k] = n
39+
return md
40+
41+
def __getitem__(self, key):
42+
return self.d[key]
43+
44+
def __setitem__(self, key, value):
45+
self.d.setdefault(key, set()).add(value)
46+
47+
def __contains__(self, key):
48+
return key in self.d

alpacloud/eztag/tag.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,38 @@
33
import re
44
from dataclasses import dataclass
55

6+
from alpacloud.eztag.multidict import MultiDict
7+
8+
69
@dataclass
710
class TagSet:
811
"""A set of tags"""
9-
ts: dict[str, str | None]
12+
13+
ts: MultiDict
1014

1115
def has(self, k: str) -> bool:
1216
"""Check if the key exists in the tagset"""
1317
return k in self.ts
1418

15-
def match(self, k: str, v: str) -> bool:
16-
"""Exact match the value for this key"""
17-
return self.ts[k] == v
19+
def match(self, k: str, v: str | None) -> bool:
20+
"""Exact match the value for this key (returns True if any value matches)"""
21+
if not self.has(k):
22+
return False
23+
return v in self.ts[k]
1824

1925
def rematch(self, k: str, v: str | re.Pattern) -> bool:
20-
"""Regex match the value for this key"""
26+
"""Regex match the value for this key (returns True if any value matches)"""
2127
if isinstance(v, str):
2228
v = re.compile(v)
2329

24-
return self.has(k) and v.fullmatch(self.ts[k]) is not None
30+
if not self.has(k):
31+
return False
32+
33+
return any(val is not None and v.fullmatch(val) is not None for val in self.ts[k])
2534

2635
def contains(self, k: str, v: str) -> bool:
27-
"""Check if the value for this key contains the substring"""
28-
return self.has(k) and self.ts[k] is not None and v in self.ts[k]
36+
"""Check if any value for this key contains the substring"""
37+
if not self.has(k):
38+
return False
39+
40+
return any(val is not None and v in val for val in self.ts[k])

0 commit comments

Comments
 (0)