From 32af8c9ecd8894178c2a5bceeebb6083c26dbf19 Mon Sep 17 00:00:00 2001 From: ChrisLovering Date: Sat, 4 Jul 2026 16:23:39 +0100 Subject: [PATCH 1/3] Replace update_or_create loop with bulk_update to fix N+1 Refreshing stale repo metadata fired a SELECT + UPDATE per repo (12 queries for 6 repos). Now fetches all existing records in one query and bulk_updates them, keeping the total at 2-3 queries regardless of repo count. --- pydis_site/apps/home/views.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/pydis_site/apps/home/views.py b/pydis_site/apps/home/views.py index 0df84478e..8fb6116cb 100644 --- a/pydis_site/apps/home/views.py +++ b/pydis_site/apps/home/views.py @@ -139,26 +139,32 @@ def _get_repo_data(self) -> list[RepositoryMetadata]: api_repositories = self._get_api_data() if not api_repositories: - return RepositoryMetadata.objects.all() + return list(RepositoryMetadata.objects.all()) - # Update or create all RepoData objects in self.repos + # Build updated objects and bulk_update in two queries (fetch + update) + existing = {r.repo_name: r for r in RepositoryMetadata.objects.all()} database_repositories = [] + new_repos = [] for api_data in api_repositories.values(): - repo_data, _created = RepositoryMetadata.objects.update_or_create( - repo_name=api_data["full_name"], - defaults={ - 'repo_name': api_data["full_name"], - 'description': api_data["description"], - 'forks': api_data["forks_count"], - 'stargazers': api_data["stargazers_count"], - 'language': api_data["language"], - } - ) - database_repositories.append(repo_data) + repo = existing.get(api_data["full_name"]) + if repo is None: + repo = RepositoryMetadata(repo_name=api_data["full_name"]) + new_repos.append(repo) + repo.description = api_data["description"] + repo.forks = api_data["forks_count"] + repo.stargazers = api_data["stargazers_count"] + repo.language = api_data["language"] + database_repositories.append(repo) + RepositoryMetadata.objects.bulk_update( + [r for r in database_repositories if r not in new_repos], + fields=["description", "forks", "stargazers", "language"], + ) + if new_repos: + RepositoryMetadata.objects.bulk_create(new_repos) return database_repositories # Otherwise, if the data is fresher than 2 minutes old, we should just return it. - return RepositoryMetadata.objects.all() + return list(RepositoryMetadata.objects.all()) def get(self, request: WSGIRequest) -> HttpResponse: """Collect repo data and render the homepage view.""" From 33dd0c74f7cca537e1b2996997e2c0dc89e22e9a Mon Sep 17 00:00:00 2001 From: ChrisLovering Date: Sat, 4 Jul 2026 16:23:54 +0100 Subject: [PATCH 2/3] Add RepoAPIData TypedDict to tighten _get_api_data return type Replaces the loose dict[str, dict[str, str]] annotation so Pylance can narrow forks_count/stargazers_count to int without casts. --- pydis_site/apps/home/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/home/views.py b/pydis_site/apps/home/views.py index 8fb6116cb..e561a73e0 100644 --- a/pydis_site/apps/home/views.py +++ b/pydis_site/apps/home/views.py @@ -1,5 +1,6 @@ import logging from json.decoder import JSONDecodeError +from typing import TypedDict import httpx from django.core.handlers.wsgi import WSGIRequest @@ -14,6 +15,14 @@ log = logging.getLogger(__name__) +class RepoAPIData(TypedDict): + full_name: str + description: str + language: str + forks_count: int + stargazers_count: int + + class HomeView(View): """The main landing page for the website.""" @@ -45,7 +54,7 @@ def __init__(self): else: self.headers = {} - def _get_api_data(self) -> dict[str, dict[str, str]]: + def _get_api_data(self) -> dict[str, RepoAPIData]: """ Call the GitHub API and get information about our repos. From 37d91754671e016d1a1d9abd47e798b0dcf8a6e5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 5 Jul 2026 17:56:04 -0400 Subject: [PATCH 3/3] Pleasure style dictator --- pydis_site/apps/home/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pydis_site/apps/home/views.py b/pydis_site/apps/home/views.py index e561a73e0..41415f846 100644 --- a/pydis_site/apps/home/views.py +++ b/pydis_site/apps/home/views.py @@ -16,6 +16,12 @@ class RepoAPIData(TypedDict): + """ + Partial typing for the GitHub API response for a repository. + + Only the fields we care about are included here. + """ + full_name: str description: str language: str