From da67143a6a292a8d64a57b8257beb78a9193bc8d Mon Sep 17 00:00:00 2001 From: MrCreosote Date: Sun, 12 Oct 2025 10:49:34 -0700 Subject: [PATCH 1/4] Add validate_usernames method includes caching. Also renames clients for less confusing stack traces --- scripts/process_unasync.py | 1 + src/kbase/auth/_async/client.py | 71 +++++++++-- src/kbase/auth/_sync/client.py | 71 +++++++++-- src/kbase/auth/client.py | 4 +- test/test_client.py | 202 ++++++++++++++++++++++++++++---- 5 files changed, 307 insertions(+), 42 deletions(-) diff --git a/scripts/process_unasync.py b/scripts/process_unasync.py index aea19ad..2bff851 100644 --- a/scripts/process_unasync.py +++ b/scripts/process_unasync.py @@ -8,6 +8,7 @@ def main(): additional_replacements = { + "AsyncKBaseAuthClient": "KBaseAuthClient", "AsyncClient": "Client", "aclose": "close", } diff --git a/src/kbase/auth/_async/client.py b/src/kbase/auth/_async/client.py index 0e3f5b8..421650b 100644 --- a/src/kbase/auth/_async/client.py +++ b/src/kbase/auth/_async/client.py @@ -18,6 +18,11 @@ from kbase.auth.exceptions import InvalidTokenError, InvalidUserError # TODO PUBLISH make a pypi kbase org and publish there +# TODO RELIABILITY could add retries for these methods, tenacity looks useful +# should be safe since they're all read only +# TODO NOW CODE make a kbase/auth.py module, move other code into _auth, and import everything +# TODO NOW CODE move Token and User into a common class +# We might want to expand exceptions to include the request ID for debugging purposes @dataclass @@ -75,14 +80,14 @@ def _check_response(r: httpx.Response): if err == 30010: # Illegal username # The auth server does some goofy stuff when propagating errors, should be cleaned up # at some point - raise InvalidUserError(resjson["error"]["message"].split(":", 3)[-1]) + raise InvalidUserError(resjson["error"]["message"].split(":", 3)[-1].strip()) # don't really see any other error codes we need to worry about - maybe disabled? # worry about it later. raise IOError("Error from KBase auth server: " + resjson["error"]["message"]) return resjson -class AsyncClient: +class AsyncKBaseAuthClient: """ A client for the KBase Authentication service. """ @@ -111,10 +116,6 @@ async def create( except: await cli.close() raise - # TODO CLIENT look through the myriad of auth clients to see what functionality we need - # TODO CLIENT cache valid user names using cachefor value from token - # TODO RELIABILITY could add retries for these methods, tenacity looks useful - # should be safe since they're all reads only return cli def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int | float]): @@ -123,12 +124,14 @@ def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int self._base_url = base_url self._token_url = base_url + "api/V2/token" self._me_url = base_url + "api/V2/me" + self._users_url = base_url + "api/V2/users/?list=" if cache_max_size < 1: raise ValueError("cache_max_size must be > 0") if not timer: raise ValueError("timer is required") self._token_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._user_cache = LRUCache(maxsize=cache_max_size, timer=timer) + self._user_name_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._cli = httpx.AsyncClient() async def __aenter__(self): @@ -168,8 +171,6 @@ async def get_token(self, token: str, on_cache_miss: Callable[[], None]=None) -> res = await self._get(self._token_url, headers={"Authorization": token}) tk = Token(**{k: v for k, v in res.items() if k in _VALID_TOKEN_FIELDS}) # TODO TEST later may want to add tests that change the cachefor value. - # Cleanest way to do this is update the auth2 service to allow setting it - # in test mode self._token_cache.set(token, tk, ttl=tk.cachefor / 1000) return tk @@ -194,7 +195,57 @@ async def get_user(self, token: str, on_cache_miss: Callable[[], None]=None) -> res = await self._get(self._me_url, headers={"Authorization": token}) u = User(**{k: v for k, v in res.items() if k in _VALID_USER_FIELDS}) # TODO TEST later may want to add tests that change the cachefor value. - # Cleanest way to do this is update the auth2 service to allow setting it - # in test mode self._user_cache.set(token, u, ttl=tk.cachefor / 1000) return u + + async def validate_usernames( + self, + token: str, + *usernames: str, + on_cache_miss: Callable[[str], None] = None + ) -> dict[str, bool]: + """ + Validate that one or more usernames exist in the auth service. + + If any of the names are illegal, an error is thrown. + + token - a valid KBase token for any user. + usernames - one or more usernames to query. + on_cache_miss - a function to call if a cache miss occurs. The single argument is the + username that was not in the cache + + Returns a dict of username -> boolean which is True if the username exists. + """ + _require_string(token, "token") + if not usernames: + return {} + # use a dict to preserve ordering for testing purposes + uns = {u.strip(): 1 for u in usernames if u.strip()} + to_return = {} + to_query = set() + for u in uns.keys(): + if self._user_name_cache.get(u, default=False): + to_return[u] = True + else: + if on_cache_miss: + on_cache_miss(u) + to_query.add(u) + if not to_query: + return to_return + res = await self._get( + self._users_url + ",".join(to_query), + headers={"Authorization": token} + ) + tk = None + for u in to_query: + exists = u in res + to_return[u] = exists + if exists: + if not tk: # minor optimization, don't get the token until it's needed + tk = await self.get_token(token) + # Usernames are permanent but can be disabled, so we expire based on time + # Don't cache non-existant names, could be created at any time and would + # be terrible UX for new users + # TODO TEST later may want to add tests that change the cachefor value. + self._user_name_cache.set(u, True, ttl=tk.cachefor / 1000) + return to_return diff --git a/src/kbase/auth/_sync/client.py b/src/kbase/auth/_sync/client.py index 6921b75..228d622 100644 --- a/src/kbase/auth/_sync/client.py +++ b/src/kbase/auth/_sync/client.py @@ -18,6 +18,11 @@ from kbase.auth.exceptions import InvalidTokenError, InvalidUserError # TODO PUBLISH make a pypi kbase org and publish there +# TODO RELIABILITY could add retries for these methods, tenacity looks useful +# should be safe since they're all read only +# TODO NOW CODE make a kbase/auth.py module, move other code into _auth, and import everything +# TODO NOW CODE move Token and User into a common class +# We might want to expand exceptions to include the request ID for debugging purposes @dataclass @@ -75,14 +80,14 @@ def _check_response(r: httpx.Response): if err == 30010: # Illegal username # The auth server does some goofy stuff when propagating errors, should be cleaned up # at some point - raise InvalidUserError(resjson["error"]["message"].split(":", 3)[-1]) + raise InvalidUserError(resjson["error"]["message"].split(":", 3)[-1].strip()) # don't really see any other error codes we need to worry about - maybe disabled? # worry about it later. raise IOError("Error from KBase auth server: " + resjson["error"]["message"]) return resjson -class Client: +class KBaseAuthClient: """ A client for the KBase Authentication service. """ @@ -111,10 +116,6 @@ def create( except: cli.close() raise - # TODO CLIENT look through the myriad of auth clients to see what functionality we need - # TODO CLIENT cache valid user names using cachefor value from token - # TODO RELIABILITY could add retries for these methods, tenacity looks useful - # should be safe since they're all reads only return cli def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int | float]): @@ -123,12 +124,14 @@ def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int self._base_url = base_url self._token_url = base_url + "api/V2/token" self._me_url = base_url + "api/V2/me" + self._users_url = base_url + "api/V2/users/?list=" if cache_max_size < 1: raise ValueError("cache_max_size must be > 0") if not timer: raise ValueError("timer is required") self._token_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._user_cache = LRUCache(maxsize=cache_max_size, timer=timer) + self._user_name_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._cli = httpx.Client() def __enter__(self): @@ -168,8 +171,6 @@ def get_token(self, token: str, on_cache_miss: Callable[[], None]=None) -> Token res = self._get(self._token_url, headers={"Authorization": token}) tk = Token(**{k: v for k, v in res.items() if k in _VALID_TOKEN_FIELDS}) # TODO TEST later may want to add tests that change the cachefor value. - # Cleanest way to do this is update the auth2 service to allow setting it - # in test mode self._token_cache.set(token, tk, ttl=tk.cachefor / 1000) return tk @@ -194,7 +195,57 @@ def get_user(self, token: str, on_cache_miss: Callable[[], None]=None) -> User: res = self._get(self._me_url, headers={"Authorization": token}) u = User(**{k: v for k, v in res.items() if k in _VALID_USER_FIELDS}) # TODO TEST later may want to add tests that change the cachefor value. - # Cleanest way to do this is update the auth2 service to allow setting it - # in test mode self._user_cache.set(token, u, ttl=tk.cachefor / 1000) return u + + def validate_usernames( + self, + token: str, + *usernames: str, + on_cache_miss: Callable[[str], None] = None + ) -> dict[str, bool]: + """ + Validate that one or more usernames exist in the auth service. + + If any of the names are illegal, an error is thrown. + + token - a valid KBase token for any user. + usernames - one or more usernames to query. + on_cache_miss - a function to call if a cache miss occurs. The single argument is the + username that was not in the cache + + Returns a dict of username -> boolean which is True if the username exists. + """ + _require_string(token, "token") + if not usernames: + return {} + # use a dict to preserve ordering for testing purposes + uns = {u.strip(): 1 for u in usernames if u.strip()} + to_return = {} + to_query = set() + for u in uns.keys(): + if self._user_name_cache.get(u, default=False): + to_return[u] = True + else: + if on_cache_miss: + on_cache_miss(u) + to_query.add(u) + if not to_query: + return to_return + res = self._get( + self._users_url + ",".join(to_query), + headers={"Authorization": token} + ) + tk = None + for u in to_query: + exists = u in res + to_return[u] = exists + if exists: + if not tk: # minor optimization, don't get the token until it's needed + tk = self.get_token(token) + # Usernames are permanent but can be disabled, so we expire based on time + # Don't cache non-existant names, could be created at any time and would + # be terrible UX for new users + # TODO TEST later may want to add tests that change the cachefor value. + self._user_name_cache.set(u, True, ttl=tk.cachefor / 1000) + return to_return diff --git a/src/kbase/auth/client.py b/src/kbase/auth/client.py index 231d0f8..9b8b353 100644 --- a/src/kbase/auth/client.py +++ b/src/kbase/auth/client.py @@ -2,5 +2,5 @@ The aync and sync versions of the KBase Auth Client. """ -from kbase.auth._async.client import AsyncClient as AsyncKBaseAuthClient # @UnusedImport -from kbase.auth._sync.client import Client as KBaseAuthClient # @UnusedImport +from kbase.auth._async.client import AsyncKBaseAuthClient # @UnusedImport +from kbase.auth._sync.client import KBaseAuthClient # @UnusedImport diff --git a/test/test_client.py b/test/test_client.py index 0c7d873..b43ce72 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -6,7 +6,7 @@ from conftest import AUTH_URL, AUTH_VERSION from kbase.auth.client import KBaseAuthClient, AsyncKBaseAuthClient -from kbase.auth.exceptions import InvalidTokenError +from kbase.auth.exceptions import InvalidTokenError, InvalidUserError async def _create_fail(url: str, expected: Exception, cachesize=1, timer=time.time): @@ -104,15 +104,15 @@ async def test_get_token_basic(auth_users): @pytest.mark.asyncio -async def test_get_token_basic_fail(auth_users): +async def test_get_token_fail(auth_users): err = "token is required and cannot be a whitespace only string" - await _get_token_basic_fail(None, ValueError(err)) - await _get_token_basic_fail(" \t ", ValueError(err)) + await _get_token_fail(None, ValueError(err)) + await _get_token_fail(" \t ", ValueError(err)) err = "KBase auth server reported token is invalid." - await _get_token_basic_fail("superfake", InvalidTokenError(err)) + await _get_token_fail("superfake", InvalidTokenError(err)) -async def _get_token_basic_fail(token: str, expected: Exception): +async def _get_token_fail(token: str, expected: Exception): with KBaseAuthClient.create(AUTH_URL) as cli: with pytest.raises(type(expected), match=f"^{expected.args[0]}$"): cli.get_token(token) @@ -187,8 +187,7 @@ async def test_get_token_cache_evict_on_time(auth_users): cachemiss = Mock() t1 = cli.get_token(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 - # TODO TEST auth2 always returns 300000 ms for cachefor. Update testmode to allow - # setting different values and test here + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky timer.advance(299) tt1 = cli.get_token(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 @@ -203,8 +202,7 @@ async def test_get_token_cache_evict_on_time(auth_users): cachemiss = Mock() t1 = await cli.get_token(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 - # TODO TEST auth2 always returns 300000 ms for cachefor. Update testmode to allow - # setting different values and test here + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky timer.advance(299) tt1 = await cli.get_token(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 @@ -238,15 +236,15 @@ async def test_get_user_basic(auth_users): @pytest.mark.asyncio -async def test_get_user_basic_fail(auth_users): +async def test_get_user_fail(auth_users): err = "token is required and cannot be a whitespace only string" - await _get_user_basic_fail(None, ValueError(err)) - await _get_user_basic_fail(" \t ", ValueError(err)) + await _get_user_fail(None, ValueError(err)) + await _get_user_fail(" \t ", ValueError(err)) err = "KBase auth server reported token is invalid." - await _get_user_basic_fail("superfake", InvalidTokenError(err)) + await _get_user_fail("superfake", InvalidTokenError(err)) -async def _get_user_basic_fail(token: str, expected: Exception): +async def _get_user_fail(token: str, expected: Exception): with KBaseAuthClient.create(AUTH_URL) as cli: with pytest.raises(type(expected), match=f"^{expected.args[0]}$"): cli.get_user(token) @@ -264,7 +262,7 @@ async def test_get_user_cache_evict_on_size(auth_users): u2 = cli.get_user(auth_users["user_random1"], on_cache_miss=cachemiss) u3 = cli.get_user(auth_users["user_random2"], on_cache_miss=cachemiss) assert cachemiss.call_count == 3 - # check userss in cache + # check users in cache uu1 = cli.get_user(auth_users["user"], on_cache_miss=cachemiss) uu2 = cli.get_user(auth_users["user_random1"], on_cache_miss=cachemiss) uu3 = cli.get_user(auth_users["user_random2"], on_cache_miss=cachemiss) @@ -311,8 +309,7 @@ async def test_get_user_cache_evict_on_time(auth_users): cachemiss = Mock() u1 = cli.get_user(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 - # TODO TEST auth2 always returns 300000 ms for cachefor. Update testmode to allow - # setting different values and test here + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky timer.advance(299) uu1 = cli.get_user(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 @@ -327,8 +324,7 @@ async def test_get_user_cache_evict_on_time(auth_users): cachemiss = Mock() u1 = await cli.get_user(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 - # TODO TEST auth2 always returns 300000 ms for cachefor. Update testmode to allow - # setting different values and test here + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky timer.advance(299) uu1 = await cli.get_user(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 1 @@ -337,3 +333,169 @@ async def test_get_user_cache_evict_on_time(auth_users): uuu1 = await cli.get_user(auth_users["user"], on_cache_miss=cachemiss) assert cachemiss.call_count == 2 assert uuu1 == u1 + + +@pytest.mark.asyncio +async def test_validate_usernames_noop(auth_users): + with KBaseAuthClient.create(AUTH_URL) as cli: + res1 = cli.validate_usernames(auth_users["user"]) + res2 = cli.validate_usernames(auth_users["user"], " \t ") + async with await AsyncKBaseAuthClient.create(AUTH_URL) as cli: + res3 = await cli.validate_usernames(auth_users["user"]) + res4 = await cli.validate_usernames(auth_users["user"], " ") + + assert res1 == {} + assert res2 == {} + assert res3 == {} + assert res4 == {} + + +@pytest.mark.asyncio +async def test_validate_usernames_basic(auth_users): + with KBaseAuthClient.create(AUTH_URL) as cli: + res1 = cli.validate_usernames( + auth_users["user"], + "user", "foo", "user_all", "whee", "user", # should ignore duplicates + ) + async with await AsyncKBaseAuthClient.create(AUTH_URL) as cli: + res2 = await cli.validate_usernames( + auth_users["user"], + "user", "baz", " \t ", "user_random1", "user_random3", "user", + ) + + assert res1 == {"foo": False, "user": True, "user_all": True, "whee": False} + assert res2 == {"baz": False, "user": True, "user_random1": True, "user_random3": False} + + +@pytest.mark.asyncio +async def test_validate_usernames_fail(auth_users): + err = "token is required and cannot be a whitespace only string" + await _validate_usernames_fail(None, [], ValueError(err)) + await _validate_usernames_fail(" \t ", [], ValueError(err)) + err = "KBase auth server reported token is invalid." + await _validate_usernames_fail("superfake", ["user"], InvalidTokenError(err)) + err = "Illegal character in user name a-b: -" + await _validate_usernames_fail("superfake", ["user", "a-b"], InvalidUserError(err)) + + +async def _validate_usernames_fail(token: str, users: list[str], expected: Exception): + with KBaseAuthClient.create(AUTH_URL) as cli: + with pytest.raises(type(expected), match=f"^{expected.args[0]}$"): + cli.validate_usernames(token, *users) + async with await AsyncKBaseAuthClient.create(AUTH_URL) as cli: + with pytest.raises(type(expected), match=f"^{expected.args[0]}$"): + await cli.validate_usernames(token, *users) + + +@pytest.mark.asyncio +async def test_validate_usernames_cache_evict_on_size(auth_users): + missed = [] + def cachemiss(user): + missed.append(user) + with KBaseAuthClient.create(AUTH_URL, cache_max_size=3) as cli: + # fill the cache + res = cli.validate_usernames( + auth_users["user"], + "user", "user_random1", "user_random2", "nouser", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random1": True, "user_random2": True, "nouser": False} + assert missed == ["user", "user_random1", "user_random2", "nouser"] + missed.clear() + # check users in cache + res = cli.validate_usernames( + auth_users["user"], + "user", "user_random1", "user_random2", "nouser", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random1": True, "user_random2": True, "nouser": False} + assert missed == ["nouser"] + missed.clear() + # Force an eviction + res = cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] + missed.clear() + # Check user was evicted + res = cli.validate_usernames( + auth_users["user"], + "user", "user_random2", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random2": True} + assert missed == ["user"] + + missed.clear() + async with await AsyncKBaseAuthClient.create(AUTH_URL, cache_max_size=3) as cli: + # fill the cache + res = await cli.validate_usernames( + auth_users["user"], + "user", "user_random1", "user_random2", "nouser", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random1": True, "user_random2": True, "nouser": False} + assert missed == ["user", "user_random1", "user_random2", "nouser"] + missed.clear() + # check users in cache + res = await cli.validate_usernames( + auth_users["user"], + "user", "user_random1", "user_random2", "nouser", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random1": True, "user_random2": True, "nouser": False} + assert missed == ["nouser"] + missed.clear() + # Force an eviction + res = await cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] + missed.clear() + # Check user was evicted + res = await cli.validate_usernames( + auth_users["user"], + "user", "user_random2", + on_cache_miss=cachemiss + ) + assert res == {"user": True, "user_random2": True} + assert missed == ["user"] + + +@pytest.mark.asyncio +async def test_validate_usernames_cache_evict_on_time(auth_users): + timer = FakeTimer() + missed = [] + def cachemiss(user): + missed.append(user) + with KBaseAuthClient.create(AUTH_URL, timer=timer) as cli: + res = cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] + missed.clear() + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky + timer.advance(299) + res = cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == [] + missed.clear() + timer.advance(2) + res = cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] + + timer = FakeTimer() + missed.clear() + async with await AsyncKBaseAuthClient.create(AUTH_URL, timer=timer) as cli: + res = await cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] + missed.clear() + # TODO TEST test with alternate cachefor values. Changing this in auth2 is clunky + timer.advance(299) + res = await cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == [] + missed.clear() + timer.advance(2) + res = await cli.validate_usernames(auth_users["user"], "user_all", on_cache_miss=cachemiss) + assert res == {"user_all": True} + assert missed == ["user_all"] From e08fefbdbb421915c4bfacf640e5ce91c0a46dca Mon Sep 17 00:00:00 2001 From: MrCreosote Date: Mon, 13 Oct 2025 09:33:30 -0700 Subject: [PATCH 2/4] user_name --> username --- src/kbase/auth/_async/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kbase/auth/_async/client.py b/src/kbase/auth/_async/client.py index 421650b..8b4412e 100644 --- a/src/kbase/auth/_async/client.py +++ b/src/kbase/auth/_async/client.py @@ -131,7 +131,7 @@ def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int raise ValueError("timer is required") self._token_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._user_cache = LRUCache(maxsize=cache_max_size, timer=timer) - self._user_name_cache = LRUCache(maxsize=cache_max_size, timer=timer) + self._username_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._cli = httpx.AsyncClient() async def __aenter__(self): @@ -224,7 +224,7 @@ async def validate_usernames( to_return = {} to_query = set() for u in uns.keys(): - if self._user_name_cache.get(u, default=False): + if self._username_cache.get(u, default=False): to_return[u] = True else: if on_cache_miss: @@ -247,5 +247,5 @@ async def validate_usernames( # Don't cache non-existant names, could be created at any time and would # be terrible UX for new users # TODO TEST later may want to add tests that change the cachefor value. - self._user_name_cache.set(u, True, ttl=tk.cachefor / 1000) + self._username_cache.set(u, True, ttl=tk.cachefor / 1000) return to_return From 19da78f45ff4032cc75f56967c9b466c896aa8cd Mon Sep 17 00:00:00 2001 From: MrCreosote Date: Mon, 13 Oct 2025 10:01:40 -0700 Subject: [PATCH 3/4] fix typo --- src/kbase/auth/_async/client.py | 2 +- src/kbase/auth/_sync/client.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/kbase/auth/_async/client.py b/src/kbase/auth/_async/client.py index 8b4412e..78a57ab 100644 --- a/src/kbase/auth/_async/client.py +++ b/src/kbase/auth/_async/client.py @@ -244,7 +244,7 @@ async def validate_usernames( if not tk: # minor optimization, don't get the token until it's needed tk = await self.get_token(token) # Usernames are permanent but can be disabled, so we expire based on time - # Don't cache non-existant names, could be created at any time and would + # Don't cache non-existent names, could be created at any time and would # be terrible UX for new users # TODO TEST later may want to add tests that change the cachefor value. self._username_cache.set(u, True, ttl=tk.cachefor / 1000) diff --git a/src/kbase/auth/_sync/client.py b/src/kbase/auth/_sync/client.py index 228d622..f5fda5b 100644 --- a/src/kbase/auth/_sync/client.py +++ b/src/kbase/auth/_sync/client.py @@ -131,7 +131,7 @@ def __init__(self, base_url: str, cache_max_size: int, timer: Callable[[[]], int raise ValueError("timer is required") self._token_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._user_cache = LRUCache(maxsize=cache_max_size, timer=timer) - self._user_name_cache = LRUCache(maxsize=cache_max_size, timer=timer) + self._username_cache = LRUCache(maxsize=cache_max_size, timer=timer) self._cli = httpx.Client() def __enter__(self): @@ -224,7 +224,7 @@ def validate_usernames( to_return = {} to_query = set() for u in uns.keys(): - if self._user_name_cache.get(u, default=False): + if self._username_cache.get(u, default=False): to_return[u] = True else: if on_cache_miss: @@ -244,8 +244,8 @@ def validate_usernames( if not tk: # minor optimization, don't get the token until it's needed tk = self.get_token(token) # Usernames are permanent but can be disabled, so we expire based on time - # Don't cache non-existant names, could be created at any time and would + # Don't cache non-existent names, could be created at any time and would # be terrible UX for new users # TODO TEST later may want to add tests that change the cachefor value. - self._user_name_cache.set(u, True, ttl=tk.cachefor / 1000) + self._username_cache.set(u, True, ttl=tk.cachefor / 1000) return to_return From 92b00f8d386d2475fc5242807ea55a01aa1ba8a0 Mon Sep 17 00:00:00 2001 From: MrCreosote Date: Mon, 13 Oct 2025 11:15:41 -0700 Subject: [PATCH 4/4] Minor simplification --- src/kbase/auth/_async/client.py | 5 ++--- src/kbase/auth/_sync/client.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/kbase/auth/_async/client.py b/src/kbase/auth/_async/client.py index 78a57ab..4905d34 100644 --- a/src/kbase/auth/_async/client.py +++ b/src/kbase/auth/_async/client.py @@ -238,9 +238,8 @@ async def validate_usernames( ) tk = None for u in to_query: - exists = u in res - to_return[u] = exists - if exists: + to_return[u] = u in res + if to_return[u]: if not tk: # minor optimization, don't get the token until it's needed tk = await self.get_token(token) # Usernames are permanent but can be disabled, so we expire based on time diff --git a/src/kbase/auth/_sync/client.py b/src/kbase/auth/_sync/client.py index f5fda5b..f1af398 100644 --- a/src/kbase/auth/_sync/client.py +++ b/src/kbase/auth/_sync/client.py @@ -238,9 +238,8 @@ def validate_usernames( ) tk = None for u in to_query: - exists = u in res - to_return[u] = exists - if exists: + to_return[u] = u in res + if to_return[u]: if not tk: # minor optimization, don't get the token until it's needed tk = self.get_token(token) # Usernames are permanent but can be disabled, so we expire based on time