Skip to content

Cache avatar url #12355

Merged
mekarpeles merged 8 commits into
internetarchive:masterfrom
Saad259:cache-avatar-url-9282
Apr 22, 2026
Merged

Cache avatar url #12355
mekarpeles merged 8 commits into
internetarchive:masterfrom
Saad259:cache-avatar-url-9282

Conversation

@Saad259
Copy link
Copy Markdown
Contributor

@Saad259 Saad259 commented Apr 13, 2026

Closes #9282

Technical

get_avatar_url had a @cache.memoize decorator that was commented out because its key was the static string "user-avatar", which would have cached one user's avatar URL for all users. This PR restores the decorator with a correct per-username key (f"user-avatar-{username}"), so each user's avatar URL is cached independently in memcache, avoiding repeated site.get() and get_account() calls for the same user across requests.
Note: the first page load still pays the full cost of 50 calls per page of 25 followers (25x site.get('/people/X') and 25x get_account()). Subsequent loads are served from cache with 0 calls per user. A bulk fetch via site.get_many() in my_follows.GET would reduce the first load from 25 individual site.get calls down to 1, but requires template changes and is flagged for a follow-up.

Testing

  1. Navigate to /people/<username>/followers or /people/<username>/following
  2. Observe that avatar images load correctly
  3. On repeat page loads, avatar requests should be served from cache rather than making fresh site.get() calls per user

Screenshot

NA no UI changes

Stakeholders

@mekarpeles

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores and fixes caching for User.get_avatar_url so avatar URL lookups are cached per-user in memcache (avoiding repeated site.get() / account lookups), and adds tests to validate correctness and caching behavior.

Changes:

  • Re-enables @cache.memoize on User.get_avatar_url with a per-username cache key.
  • Adds unit tests verifying avatar URL generation and caching behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
openlibrary/core/models.py Reintroduces memcache memoization for avatar URL lookup using a per-username cache key.
openlibrary/plugins/upstream/tests/test_models.py Adds tests for correct avatar URL generation and caching semantics.
Comments suppressed due to low confidence (1)

openlibrary/core/models.py:1060

  • This memoized value is cached indefinitely (default expires=0). If an account is created/linked later or internetarchive_itemname is temporarily missing, the function can return a bad URL (e.g. ending in None) and that result will be cached until memcache eviction. Consider adding a reasonable expires (e.g. hours/day) and/or using the cacheable parameter to avoid caching when itemname is falsy.
    @cache.memoize(
        engine="memcache", key=lambda cls, username: f"user-avatar-{username}"
    )
    def get_avatar_url(cls, username: str) -> str:
        username = username.rsplit('/people/', maxsplit=1)[-1]
        user = web.ctx.site.get(f'/people/{username}')
        itemname = user.get_account().get('internetarchive_itemname')

        return f'https://archive.org/services/img/{itemname}'

Comment on lines +1052 to 1056
@cache.memoize(
engine="memcache", key=lambda cls, username: f"user-avatar-{username}"
)
def get_avatar_url(cls, username: str) -> str:
username = username.rsplit('/people/', maxsplit=1)[-1]
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memoize key is computed from the raw username argument, but get_avatar_url later normalizes username by stripping a /people/ prefix. If callers pass both forms (e.g. "foo" vs "/people/foo"), this will create separate cache entries and reduce cache hit rate. Consider normalizing inside the key function (or normalize username before memoization) so both inputs map to the same cache key.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +128
def test_caches_result_for_same_username(self, monkeypatch):
web.ctx.site.save({"key": "/people/cachetest_user", "type": {"key": "/type/user"}})
web.ctx.site.store["account/cachetest_user"] = {
"internetarchive_itemname": "@cachetest_user-archive",
}
call_count = 0
original_get = web.ctx.site.get

def counting_get(key, *args, **kwargs):
nonlocal call_count
call_count += 1
return original_get(key, *args, **kwargs)

monkeypatch.setattr(web.ctx.site, "get", counting_get)
models.User.get_avatar_url("cachetest_user")
models.User.get_avatar_url("cachetest_user")
assert call_count == 1
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests exercise the memcache-backed decorator, but they don't clear the global memcache between tests/runs. If the user-avatar-* key already exists (e.g. when re-running a subset of tests in the same process), call_count could be 0 and the test would fail. Consider deleting the relevant memcache key(s) at the start of the test (or flushing the mock memcache in setup_method) to make the caching assertions deterministic.

Copilot uses AI. Check for mistakes.
@mekarpeles mekarpeles merged commit 0f3534d into internetarchive:master Apr 22, 2026
3 checks passed
IvanPisquiy06 pushed a commit to IvanPisquiy06/openlibrary that referenced this pull request Apr 27, 2026
IvanPisquiy06 pushed a commit to IvanPisquiy06/openlibrary that referenced this pull request Apr 27, 2026
Sadashii pushed a commit to Sadashii/openlibrary that referenced this pull request May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cache get_avatar_url on /followers page to avoid fetching every user full object

3 participants