Skip to content

Commit b3ab2d8

Browse files
authored
remove maxmind (#384)
1 parent ca1815c commit b3ab2d8

File tree

3 files changed

+34
-122
lines changed

3 files changed

+34
-122
lines changed

backend/proteins/views/protein.py

Lines changed: 34 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88

99
import django.forms
1010
import django.forms.formsets
11+
import requests
1112
import reversion
1213
from django.apps import apps
1314
from django.conf import settings
1415
from django.contrib import messages
1516
from django.contrib.admin.views.decorators import staff_member_required
1617
from django.contrib.auth.decorators import login_required
18+
from django.core.cache import cache
1719
from django.core.mail import mail_admins, mail_managers
1820
from django.db import transaction
1921
from django.db.models import Case, Count, F, Func, Prefetch, Q, Value, When
@@ -56,8 +58,6 @@
5658
from ..models import BleachMeasurement, Excerpt, Organism, Protein, Spectrum, State
5759

5860
if TYPE_CHECKING:
59-
import maxminddb
60-
6161
from proteins.forms.forms import BaseStateFormSet
6262

6363
logger = logging.getLogger(__name__)
@@ -122,82 +122,43 @@ def __init__(self, response):
122122
self.response = response
123123

124124

125-
def maxmind_db() -> str:
126-
"""Create and return a temporary file containing the MaxMind database.
125+
def _mask_ip_for_caching(ip: str) -> str:
126+
"""Mask IP to /16 (IPv4) or /48 (IPv6) for caching."""
127+
if not ip or not isinstance(ip, str):
128+
return ""
129+
if ":" in ip: # IPv6
130+
# Mask to /48 (first 3 groups)
131+
parts = ip.split(":")
132+
return ":".join(parts[:3]) + "::" if len(parts) >= 3 else ip
133+
else: # IPv4
134+
parts = ip.split(".")
135+
return f"{parts[0]}.{parts[1]}.0.0" if len(parts) == 4 else ""
127136

128-
Uses Django cache to store the database path across workers.
129-
The file persists on disk but the path is cached for 24 hours.
130-
"""
131-
import io
132-
import os
133-
import tarfile
134-
import tempfile
135-
136-
import requests
137-
from django.conf import settings
138-
from django.core.cache import cache
139-
140-
# Try to get cached path first
141-
cache_key = "maxmind_db_path"
142-
cached_path = cache.get(cache_key)
143-
if cached_path and os.path.exists(cached_path):
144-
return cached_path
145-
146-
# Download and cache the database
147-
url = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key={}&suffix=tar.gz"
148-
url = url.format(settings.MAXMIND_API_KEY)
149-
response = requests.get(url)
150-
response.raise_for_status()
151-
with tarfile.open(fileobj=io.BytesIO(response.content), mode="r:gz") as tar:
152-
for member in tar.getmembers():
153-
if member.name.endswith(".mmdb"):
154-
mmdb_file = tar.extractfile(member)
155-
if mmdb_file is not None:
156-
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mmdb")
157-
tmp.write(mmdb_file.read())
158-
tmp.close()
159-
# Cache the path for 24 hours
160-
cache.set(cache_key, tmp.name, 60 * 60 * 24)
161-
return tmp.name
162-
return ""
163-
164-
165-
def maxmind_reader() -> "maxminddb.Reader | None":
166-
"""Get MaxMind database reader.
167-
168-
Uses Django cache to minimize memory usage. The reader is cached
169-
for 1 hour to balance memory usage and performance.
170-
"""
171-
from django.core.cache import cache
172-
from maxminddb import open_database
173137

174-
cache_key = "maxmind_reader"
175-
reader = cache.get(cache_key)
176-
if reader is not None:
177-
return reader
138+
def get_country_code(request) -> str:
139+
"""Get country code from IP address using cached API lookup."""
140+
141+
x_forwarded_for = request.headers.get("x-forwarded-for")
142+
if x_forwarded_for:
143+
ip = x_forwarded_for.split(",")[0].strip()
144+
else:
145+
ip = request.META.get("REMOTE_ADDR")
178146

179147
try:
180-
if db := maxmind_db():
181-
reader = open_database(db)
182-
# Cache reader for 1 hour
183-
cache.set(cache_key, reader, 60 * 60)
184-
return reader
148+
if not (masked_ip := _mask_ip_for_caching(ip)):
149+
return ""
150+
151+
cache_key = f"country_code:{masked_ip}"
152+
country_code = cache.get(cache_key)
153+
if country_code is not None:
154+
return country_code
155+
156+
response = requests.get(f"https://ipapi.co/{ip}/country/", timeout=2)
157+
country_code = response.text.strip()
158+
cache.set(cache_key, country_code, 14 * 60 * 60 * 24) # cache for 14 days
159+
return country_code
185160
except Exception:
186-
pass
187-
return None
188-
189-
190-
def get_country_code(request) -> str:
191-
# Definitely should be used inside a try/exc block
192-
if reader := maxmind_reader():
193-
x_forwarded_for = request.headers.get("x-forwarded-for")
194-
if x_forwarded_for:
195-
ip = x_forwarded_for.split(",")[0]
196-
else:
197-
ip = request.META.get("REMOTE_ADDR")
198-
if response := reader.get(ip):
199-
return str(response["country"]["iso_code"]) # pyright: ignore[reportIndexIssue]
200-
return ""
161+
return ""
201162

202163

203164
class ProteinDetailView(DetailView):

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ dependencies = [
3535
'graphene>=3.2.2',
3636
'habanero>=1.2.3',
3737
'matplotlib>=3.7.1',
38-
'maxminddb>=2.5.1',
3938
'numpy>=1.26.3',
4039
'Pillow>=10.0.0',
4140
'psycopg2>=2.9.11; platform_system == "Linux"',

uv.lock

Lines changed: 0 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)