Skip to content

Commit

Permalink
Allow IdentityMap to consume a cache on init
Browse files Browse the repository at this point in the history
This allows multiple IdentityMap instances to share a cache by
allowing users to pass a cache to the IdentityMap.

Naive usage still looks identical, with an automatic dict wrapped
inside of the map object.

Add more tests to explore this behavior.
  • Loading branch information
sirosen committed Jan 24, 2020
1 parent e399699 commit bbf7983
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 2 deletions.
20 changes: 18 additions & 2 deletions globus_sdk/auth/identity_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ class IdentityMap(object):
A list or other iterable of usernames or identity IDs (potentially mixed together)
which will be used to seed the ``IdentityMap`` 's tracking of unresolved Identities.
``cache`` (*Mapping*)
A dict or other mapping object which will be used to cache results. The default is
that results are cached once per IdentityMap object. If you want multiple
IdentityMaps to share data, explicitly pass the same ``cache`` to both.
``id_batch_size`` (*int*)
A non-default batch size to use when communicating with Globus Auth. Leaving this
set to the default is strongly recommended.
Expand All @@ -131,7 +136,7 @@ class IdentityMap(object):

_default_id_batch_size = 100

def __init__(self, auth_client, identity_ids=None, id_batch_size=None):
def __init__(self, auth_client, identity_ids=None, cache=None, id_batch_size=None):
self.auth_client = auth_client
self.id_batch_size = id_batch_size or self._default_id_batch_size

Expand All @@ -141,7 +146,9 @@ def __init__(self, auth_client, identity_ids=None, id_batch_size=None):
)

# the cache is a dict mapping IDs and Usernames
self._cache = {}
# a cache may be passed in via the constructor in order to make multiple
# IdentityMap objects share a cache
self._cache = cache if cache is not None else {}

def _fetch_batch_including(self, key):
"""
Expand Down Expand Up @@ -217,6 +224,15 @@ def __getitem__(self, key):
"""
if key not in self._cache:
self._fetch_batch_including(key)
# if the key was in the cache, double-check that it's not in the unresolved
# usernames or IDs for the IdentityMap
# otherwise, when a cache is being shared between IdentityMap instances, it's
# possible to call out to Auth unnecessarily
else:
if key in self.unresolved_ids:
self.unresolved_ids.remove(key)
elif key in self.unresolved_usernames:
self.unresolved_usernames.remove(key)
return self._cache[key]

def __delitem__(self, key):
Expand Down
60 changes: 60 additions & 0 deletions tests/functional/auth/test_identity_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,63 @@ def test_identity_map_del(client):
# invalidated the cached ID data and are asking the IDMap to look it up again
assert idmap.get(identity_id)["username"] == "sirosen@globus.org"
assert len(httpretty.httpretty.latest_requests) == 2


@pytest.mark.parametrize(
"lookup1,lookup2",
[
("sirosen@globus.org", "sirosen@globus.org"),
("sirosen@globus.org", "ae341a98-d274-11e5-b888-dbae3a8ba545"),
("ae341a98-d274-11e5-b888-dbae3a8ba545", "sirosen@globus.org"),
],
)
@pytest.mark.parametrize("initial_add", [True, False])
def test_identity_map_shared_cache_match(client, initial_add, lookup1, lookup2):
register_api_route("auth", "/v2/api/identities", body=IDENTITIES_SINGLE_RESPONSE)
cache = {}
idmap1 = globus_sdk.IdentityMap(client, cache=cache)
idmap2 = globus_sdk.IdentityMap(client, cache=cache)
if initial_add:
idmap1.add(lookup1)
idmap2.add(lookup2)
# no requests yet...
assert len(httpretty.httpretty.latest_requests) == 0
# do the first lookup, it should make one request
assert idmap1[lookup1]["organization"] == "Globus Team"
assert len(httpretty.httpretty.latest_requests) == 1
# lookup more values and make sure that "everything matches"
assert idmap2[lookup2]["organization"] == "Globus Team"
assert idmap1[lookup1]["id"] == idmap2[lookup2]["id"]
assert idmap1[lookup2]["id"] == idmap2[lookup1]["id"]
# we've only made one request, because the shared cache captured this info on the
# very first call
assert len(httpretty.httpretty.latest_requests) == 1


@pytest.mark.parametrize(
"lookup1,lookup2",
[
("sirosen@globus.org", "globus@globus.org"),
(
"46bd0f56-e24f-11e5-a510-131bef46955c",
"ae341a98-d274-11e5-b888-dbae3a8ba545",
),
],
)
def test_identity_map_shared_cache_mismatch(client, lookup1, lookup2):
register_api_route("auth", "/v2/api/identities", body=IDENTITIES_MULTIPLE_RESPONSE)
cache = {}
idmap1 = globus_sdk.IdentityMap(client, [lookup1], cache=cache)
idmap2 = globus_sdk.IdentityMap(client, [lookup2], cache=cache)
# no requests yet...
assert len(httpretty.httpretty.latest_requests) == 0
# do the first lookup, it should make one request
assert lookup1 in (idmap1[lookup1]["id"], idmap1[lookup1]["username"])
assert len(httpretty.httpretty.latest_requests) == 1
# lookup more values and make sure that "everything matches"
assert lookup2 in (idmap2[lookup2]["id"], idmap2[lookup2]["username"])
assert idmap1[lookup1]["id"] == idmap2[lookup1]["id"]
assert idmap1[lookup2]["id"] == idmap2[lookup2]["id"]
# we've only made one request, because the shared cache captured this info on the
# very first call
assert len(httpretty.httpretty.latest_requests) == 1

0 comments on commit bbf7983

Please sign in to comment.