Skip to content

Commit

Permalink
feat: TwoFactorStorage is now a recursive data type when using .find()
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Jan 17, 2024
1 parent be86df5 commit 1f4847f
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 7 deletions.
28 changes: 23 additions & 5 deletions src/twofas/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def __init__(self, _klass: typing.Type[T_TwoFactorAuthDetails] = None) -> None:
self._multidict = defaultdict(list) # one name can map to multiple keys
self.count = 0

def __len__(self) -> int:
return self.count

def __bool__(self) -> bool:
return self.count > 0

Expand Down Expand Up @@ -58,19 +61,25 @@ def _fuzzy_find(self, find: typing.Optional[str], fuzz_threshold: int) -> list[T
return flat

# search in value:
# str is short, repr is json
return [
# search in value instead
v
for v in list(self)
if fuzzy_match(str(v).upper(), find) > fuzz_threshold
if fuzzy_match(repr(v).upper(), find) > fuzz_threshold
]

def find(self, target: Optional[str] = None, fuzz_threshold: int = 75) -> list[T_TwoFactorAuthDetails]:
def generate(self) -> list[tuple[str, str]]:
return [(_.name, _.generate()) for _ in self]

def find(
self, target: Optional[str] = None, fuzz_threshold: int = 75
) -> "TwoFactorStorage[T_TwoFactorAuthDetails]":
# first try exact match:
if items := self._multidict.get(target or ""):
return items
return new_auth_storage(items)
# else: fuzzy match:
return self._fuzzy_find(target, fuzz_threshold)
return new_auth_storage(self._fuzzy_find(target, fuzz_threshold))

def all(self) -> list[T_TwoFactorAuthDetails]:
return list(self)
Expand All @@ -83,13 +92,22 @@ def __repr__(self) -> str:
return f"<TwoFactorStorage with {len(self._multidict)} keys and {self.count} entries>"


def new_auth_storage(initial_items: list[T_TwoFactorAuthDetails] = None) -> TwoFactorStorage[T_TwoFactorAuthDetails]:
storage: TwoFactorStorage[T_TwoFactorAuthDetails] = TwoFactorStorage()

if initial_items:
storage.add(initial_items)

return storage


def load_services(filename: str, _max_retries: int = 0) -> TwoFactorStorage[TwoFactorAuthDetails]:
filepath = Path(filename)
with filepath.open() as f:
data_raw = f.read()
data = json.loads(data_raw)

storage = TwoFactorStorage(TwoFactorAuthDetails)
storage: TwoFactorStorage[TwoFactorAuthDetails] = new_auth_storage()

if decrypted := data["services"]:
services = into_class(decrypted, TwoFactorAuthDetails)
Expand Down
18 changes: 16 additions & 2 deletions tests/test_decrypted.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ def test_load(services):
def test_search_exact(services):
found = services.find("Example 1")
assert len(found) == 2
assert found == services["Example 1"]
assert list(found) == services["Example 1"]


def test_search_fuzzy(services):
assert services.find() == services.all()
assert list(services.find()) == services.all()

found = services.find("Example")
assert len(found) == 4
Expand All @@ -68,3 +68,17 @@ def test_search_fuzzy(services):

found = services.find("___")
assert len(found) == 0

# search in value

found = services.find("@google")
assert len(found) == 2

found = services.find("Additional inof") # fuzzy with typo
assert len(found) == 2

# test nested search:
found = services.find("1").find("other")
assert len(found) == 1

assert [_[1] for _ in services.generate()] == [_[1] for _ in services.find().generate()] == [_.generate() for _ in services] # generate all

0 comments on commit 1f4847f

Please sign in to comment.