refactor: 移除 2FA 功能与三个冗余依赖

2FA 早已是死代码:TOTP 相关端点标注为前端未调用,前端唯一的
two_factor_auth 是硬编码 false,没有任何入口能打开它。

- 删除 TwoFactorAuthAPI、CheckTFARequiredAPI、_totp* 辅助函数、
  TwoFactorAuthCodeSerializer 及各 serializer 的 tfa_code 字段
- 删除管理端写 two_factor_auth/tfa_token 的分支。登录已不再校验
  TOTP,若保留写入路径,置 True 会变成静默的安全降级
- 删除 User.two_factor_auth / tfa_token 字段(迁移 0009)
- 移除 django-dbconn-retry:仅挂在 INSTALLED_APPS,代码零引用。
  DATABASES 未配置 CONN_MAX_AGE(默认 0),本就没有持久连接需要
  重连修复。要开持久连接用 Django 自带的 CONN_HEALTH_CHECKS
- requests 换成 httpx(openai 已经引入):删除废弃的
  ReleaseNotesAPI,judge dispatcher 显式传 timeout=None 以保持
  requests 原本的无超时行为,判题请求是同步等结果的

依赖净减 5 个:otpauth、qrcode、requests、charset-normalizer、
django-dbconn-retry

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 23:00:45 -06:00
parent 7be5975d88
commit 989b33d827
13 changed files with 56 additions and 363 deletions

View File

@@ -0,0 +1,21 @@
# Generated by Django 6.0.4 on 2026-08-06 04:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0008_remove_userprofile_oi_problems_status_and_more'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='tfa_token',
),
migrations.RemoveField(
model_name='user',
name='two_factor_auth',
),
]

View File

@@ -40,8 +40,6 @@ class User(AbstractBaseUser):
reset_password_token_expire_time = models.DateTimeField(null=True) reset_password_token_expire_time = models.DateTimeField(null=True)
# SSO auth token # SSO auth token
auth_token = models.TextField(null=True) auth_token = models.TextField(null=True)
two_factor_auth = models.BooleanField(default=False, db_default=False)
tfa_token = models.TextField(null=True)
session_keys = JSONField(default=list, db_default=models.Value([], output_field=models.JSONField())) session_keys = JSONField(default=list, db_default=models.Value([], output_field=models.JSONField()))
# open api key # open api key
open_api = models.BooleanField(default=False, db_default=False) open_api = models.BooleanField(default=False, db_default=False)

View File

@@ -8,7 +8,6 @@ from .models import AdminType, ProblemPermission, User, UserProfile
class UserLoginSerializer(serializers.Serializer): class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField() username = serializers.CharField()
password = serializers.CharField() password = serializers.CharField()
tfa_code = serializers.CharField(required=False, allow_blank=True)
class UsernameOrEmailCheckSerializer(serializers.Serializer): class UsernameOrEmailCheckSerializer(serializers.Serializer):
@@ -26,13 +25,11 @@ class UserRegisterSerializer(serializers.Serializer):
class UserChangePasswordSerializer(serializers.Serializer): class UserChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField() old_password = serializers.CharField()
new_password = serializers.CharField(min_length=6) new_password = serializers.CharField(min_length=6)
tfa_code = serializers.CharField(required=False, allow_blank=True)
class UserChangeEmailSerializer(serializers.Serializer): class UserChangeEmailSerializer(serializers.Serializer):
password = serializers.CharField() password = serializers.CharField()
new_email = serializers.EmailField(max_length=64) new_email = serializers.EmailField(max_length=64)
tfa_code = serializers.CharField(required=False, allow_blank=True)
class GenerateUserSerializer(serializers.Serializer): class GenerateUserSerializer(serializers.Serializer):
@@ -61,7 +58,6 @@ class UserAdminSerializer(serializers.ModelSerializer):
"real_name", "real_name",
"create_time", "create_time",
"last_login", "last_login",
"two_factor_auth",
"open_api", "open_api",
"is_disabled", "is_disabled",
"raw_password", "raw_password",
@@ -89,7 +85,6 @@ class UserSerializer(serializers.ModelSerializer):
"problem_permission", "problem_permission",
"create_time", "create_time",
"last_login", "last_login",
"two_factor_auth",
"open_api", "open_api",
"is_disabled", "is_disabled",
"class_name", "class_name",
@@ -121,7 +116,6 @@ class EditUserSerializer(serializers.Serializer):
admin_type = serializers.ChoiceField(choices=AdminType.choices) admin_type = serializers.ChoiceField(choices=AdminType.choices)
problem_permission = serializers.ChoiceField(choices=ProblemPermission.choices) problem_permission = serializers.ChoiceField(choices=ProblemPermission.choices)
open_api = serializers.BooleanField() open_api = serializers.BooleanField()
two_factor_auth = serializers.BooleanField()
is_disabled = serializers.BooleanField() is_disabled = serializers.BooleanField()
class_name = serializers.CharField(required=False, allow_null=True, allow_blank=True) class_name = serializers.CharField(required=False, allow_null=True, allow_blank=True)
@@ -152,10 +146,6 @@ class SSOSerializer(serializers.Serializer):
token = serializers.CharField() token = serializers.CharField()
class TwoFactorAuthCodeSerializer(serializers.Serializer):
code = serializers.IntegerField()
class ImageUploadForm(forms.Form): class ImageUploadForm(forms.Form):
image = forms.FileField() image = forms.FileField()

View File

@@ -6,13 +6,11 @@ from ..views.oj import (
SSOAPI, SSOAPI,
ApplyResetPasswordAPI, ApplyResetPasswordAPI,
AvatarUploadAPI, AvatarUploadAPI,
CheckTFARequiredAPI,
Metrics, Metrics,
OpenAPIAppkeyAPI, OpenAPIAppkeyAPI,
ProfileProblemDisplayIDRefreshAPI, ProfileProblemDisplayIDRefreshAPI,
ResetPasswordAPI, ResetPasswordAPI,
SessionManagementAPI, SessionManagementAPI,
TwoFactorAuthAPI,
UserActivityRankAPI, UserActivityRankAPI,
UserChangeEmailAPI, UserChangeEmailAPI,
UserChangePasswordAPI, UserChangePasswordAPI,
@@ -39,11 +37,6 @@ urlpatterns = [
path("profile/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()), path("profile/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()),
path("metrics", Metrics.as_view()), path("metrics", Metrics.as_view()),
path("upload_avatar", AvatarUploadAPI.as_view()), path("upload_avatar", AvatarUploadAPI.as_view()),
path("tfa_required", CheckTFARequiredAPI.as_view()), # DEPRECATED: 前端未调用
path(
"two_factor_auth", # DEPRECATED: 前端未调用
TwoFactorAuthAPI.as_view(),
),
path("user_rank", UserRankAPI.as_view()), path("user_rank", UserRankAPI.as_view()),
path("user_activity_rank", UserActivityRankAPI.as_view()), path("user_activity_rank", UserActivityRankAPI.as_view()),
path("user_problem_rank", UserProblemRankAPI.as_view()), path("user_problem_rank", UserProblemRankAPI.as_view()),

View File

@@ -112,15 +112,6 @@ class UserAdminAPI(APIView):
user.open_api_appkey = None user.open_api_appkey = None
user.open_api = data["open_api"] user.open_api = data["open_api"]
if data["two_factor_auth"]:
# Avoid reset user tfa_token after saving changes
if not user.two_factor_auth:
user.tfa_token = rand_str()
else:
user.tfa_token = None
user.two_factor_auth = data["two_factor_auth"]
user.save() user.save()
if pre_username != user.username: if pre_username != user.username:
Submission.objects.filter(username=pre_username).update(username=user.username) Submission.objects.filter(username=pre_username).update(username=user.username)

View File

@@ -3,7 +3,6 @@ import os
from datetime import timedelta from datetime import timedelta
from importlib import import_module from importlib import import_module
import qrcode
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.db.models import Count, Q from django.db.models import Count, Q
@@ -12,7 +11,6 @@ from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from otpauth import TOTP
from options.options import SysOptions from options.options import SysOptions
from problem.models import Problem from problem.models import Problem
@@ -21,7 +19,7 @@ from utils.api import APIView, AsyncAPIView, CSRFExemptAPIView, validate_seriali
from utils.async_helpers import async_cache_get, async_cache_set from utils.async_helpers import async_cache_get, async_cache_set
from utils.captcha import Captcha from utils.captcha import Captcha
from utils.constants import CacheKey from utils.constants import CacheKey
from utils.shortcuts import datetime2str, img2base64, rand_str from utils.shortcuts import datetime2str, rand_str
from ..decorators import login_required from ..decorators import login_required
from ..models import AdminType, User, UserProfile from ..models import AdminType, User, UserProfile
@@ -32,7 +30,6 @@ from ..serializers import (
RankInfoSerializer, RankInfoSerializer,
ResetPasswordSerializer, ResetPasswordSerializer,
SSOSerializer, SSOSerializer,
TwoFactorAuthCodeSerializer,
UserChangeEmailSerializer, UserChangeEmailSerializer,
UserChangePasswordSerializer, UserChangePasswordSerializer,
UserLoginSerializer, UserLoginSerializer,
@@ -43,22 +40,6 @@ from ..serializers import (
from ..tasks import send_email_async from ..tasks import send_email_async
def _totp(token):
return TOTP(token.encode("utf-8"))
def _totp_uri(token, label, issuer):
return _totp(token).to_uri(label, issuer)
def _valid_totp(token, code):
try:
code = int(code)
except (TypeError, ValueError):
return False
return _totp(token).verify(code)
class UserProfileAPI(AsyncAPIView): class UserProfileAPI(AsyncAPIView):
@method_decorator(ensure_csrf_cookie) @method_decorator(ensure_csrf_cookie)
async def get(self, request, **kwargs): async def get(self, request, **kwargs):
@@ -136,72 +117,6 @@ class AvatarUploadAPI(AsyncAPIView):
return self.success("Succeeded") return self.success("Succeeded")
# DEPRECATED: 前端未调用 (2026-05-26)
class TwoFactorAuthAPI(APIView):
@login_required
def get(self, request):
"""
Get QR code
"""
user = request.user
if user.two_factor_auth:
return self.error("2FA is already turned on")
token = rand_str()
user.tfa_token = token
user.save()
label = f"{SysOptions.website_name_shortcut}:{user.username}"
image = qrcode.make(_totp_uri(token, label, SysOptions.website_name.replace(" ", "")))
return self.success(img2base64(image))
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def post(self, request):
"""
Open 2FA
"""
code = request.data["code"]
user = request.user
if _valid_totp(user.tfa_token, code):
user.two_factor_auth = True
user.save()
return self.success("Succeeded")
else:
return self.error("Invalid code")
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def put(self, request):
code = request.data["code"]
user = request.user
if not user.two_factor_auth:
return self.error("2FA is already turned off")
if _valid_totp(user.tfa_token, code):
user.two_factor_auth = False
user.save()
return self.success("Succeeded")
else:
return self.error("Invalid code")
# DEPRECATED: 前端未调用 (2026-05-26)
class CheckTFARequiredAPI(APIView):
@validate_serializer(UsernameOrEmailCheckSerializer)
def post(self, request):
"""
Check TFA is required
"""
data = request.data
result = False
if data.get("username"):
try:
user = User.objects.get(username=data["username"])
result = user.two_factor_auth
except User.DoesNotExist:
pass
return self.success({"result": result})
class UserLoginAPI(AsyncAPIView): class UserLoginAPI(AsyncAPIView):
@validate_serializer(UserLoginSerializer) @validate_serializer(UserLoginSerializer)
async def post(self, request): async def post(self, request):
@@ -210,22 +125,10 @@ class UserLoginAPI(AsyncAPIView):
if user: if user:
if user.is_disabled: if user.is_disabled:
return self.error("Your account has been disabled") return self.error("Your account has been disabled")
if not user.two_factor_auth:
prev_login = user.last_login prev_login = user.last_login
await auth.alogin(request, user) await auth.alogin(request, user)
request.session["prev_login"] = datetime2str(prev_login) if prev_login else "" request.session["prev_login"] = datetime2str(prev_login) if prev_login else ""
return self.success("Succeeded") return self.success("Succeeded")
if user.two_factor_auth and "tfa_code" not in data:
return self.error("tfa_required")
if _valid_totp(user.tfa_token, data["tfa_code"]):
prev_login = user.last_login
await auth.alogin(request, user)
request.session["prev_login"] = datetime2str(prev_login) if prev_login else ""
return self.success("Succeeded")
else:
return self.error("Invalid two factor verification code")
else: else:
return self.error("Invalid username or password") return self.error("Invalid username or password")
@@ -284,11 +187,6 @@ class UserChangeEmailAPI(APIView):
data = request.data data = request.data
user = auth.authenticate(username=request.user.username, password=data["password"]) user = auth.authenticate(username=request.user.username, password=data["password"])
if user: if user:
if user.two_factor_auth:
if "tfa_code" not in data:
return self.error("tfa_required")
if not _valid_totp(user.tfa_token, data["tfa_code"]):
return self.error("Invalid two factor verification code")
data["new_email"] = data["new_email"].lower() data["new_email"] = data["new_email"].lower()
if User.objects.filter(email=data["new_email"]).exists(): if User.objects.filter(email=data["new_email"]).exists():
return self.error("The email is owned by other account") return self.error("The email is owned by other account")
@@ -311,11 +209,6 @@ class UserChangePasswordAPI(APIView):
username = request.user.username username = request.user.username
user = auth.authenticate(username=username, password=data["old_password"]) user = auth.authenticate(username=username, password=data["old_password"])
if user: if user:
if user.two_factor_auth:
if "tfa_code" not in data:
return self.error("tfa_required")
if not _valid_totp(user.tfa_token, data["tfa_code"]):
return self.error("Invalid two factor verification code")
user.set_password(data["new_password"]) user.set_password(data["new_password"])
user.save() user.save()
return self.success("Succeeded") return self.success("Succeeded")
@@ -373,7 +266,6 @@ class ResetPasswordAPI(APIView):
if user.reset_password_token_expire_time < now(): if user.reset_password_token_expire_time < now():
return self.error("Token has expired") return self.error("Token has expired")
user.reset_password_token = None user.reset_password_token = None
user.two_factor_auth = False
user.set_password(data["password"]) user.set_password(data["password"])
user.save() user.save()
return self.success("Succeeded") return self.success("Succeeded")

View File

@@ -5,7 +5,6 @@ from ..views import (
DashboardInfoAPI, DashboardInfoAPI,
JudgeServerAPI, JudgeServerAPI,
RandomUsernameAPI, RandomUsernameAPI,
ReleaseNotesAPI,
SMTPTestAPI, SMTPTestAPI,
TestCasePruneAPI, TestCasePruneAPI,
WebsiteConfigAPI, WebsiteConfigAPI,
@@ -18,6 +17,5 @@ urlpatterns = [
path("random_user", RandomUsernameAPI.as_view()), path("random_user", RandomUsernameAPI.as_view()),
path("judge_server", JudgeServerAPI.as_view()), path("judge_server", JudgeServerAPI.as_view()),
path("prune_test_case", TestCasePruneAPI.as_view()), path("prune_test_case", TestCasePruneAPI.as_view()),
path("versions", ReleaseNotesAPI.as_view()), # DEPRECATED: 前端未调用
path("dashboard_info", DashboardInfoAPI.as_view()), path("dashboard_info", DashboardInfoAPI.as_view()),
] ]

View File

@@ -1,19 +1,15 @@
import asyncio import asyncio
import hashlib import hashlib
import json
import os import os
import random import random
import re import re
import shutil import shutil
import smtplib import smtplib
import time
from datetime import timedelta from datetime import timedelta
import requests
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from requests.exceptions import RequestException
from account.decorators import super_admin_required from account.decorators import super_admin_required
from account.models import User from account.models import User
@@ -253,23 +249,6 @@ class TestCasePruneAPI(APIView):
shutil.rmtree(test_case_dir, ignore_errors=True) shutil.rmtree(test_case_dir, ignore_errors=True)
# DEPRECATED: 前端未调用 (2026-05-26)
class ReleaseNotesAPI(APIView):
def get(self, request):
try:
resp = requests.get(
"https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json?_=" + str(time.time()),
timeout=3,
)
releases = resp.json()
except (RequestException, ValueError):
return self.success()
with open("docs/data.json", "r") as f:
local_version = json.load(f)["update"][0]["version"]
releases["local_version"] = local_version
return self.success(releases)
class DashboardInfoAPI(AsyncAPIView): class DashboardInfoAPI(AsyncAPIView):
async def get(self, request): async def get(self, request):
now = timezone.now() now = timezone.now()

View File

@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv export --format requirements.txt --no-dev -o deploy/requirements.txt # uv export --format requirements.txt
annotated-types==0.7.0 \ annotated-types==0.7.0 \
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
@@ -25,7 +25,6 @@ certifi==2026.4.22 \
# via # via
# httpcore # httpcore
# httpx # httpx
# requests
# sentry-sdk # sentry-sdk
channels==4.3.2 \ channels==4.3.2 \
--hash=sha256:f2bb6bfb73ad7fb4705041d07613c7b4e69528f01ef8cb9fb6c21d9295f15667 \ --hash=sha256:f2bb6bfb73ad7fb4705041d07613c7b4e69528f01ef8cb9fb6c21d9295f15667 \
@@ -37,62 +36,6 @@ channels-redis==4.3.0 \
--hash=sha256:48f3e902ae2d5fef7080215524f3b4a1d3cea4e304150678f867a1a822c0d9f5 \ --hash=sha256:48f3e902ae2d5fef7080215524f3b4a1d3cea4e304150678f867a1a822c0d9f5 \
--hash=sha256:740ee7b54f0e28cf2264a940a24453d3f00526a96931f911fcb69228ef245dd2 --hash=sha256:740ee7b54f0e28cf2264a940a24453d3f00526a96931f911fcb69228ef245dd2
# via onlinejudge # via onlinejudge
charset-normalizer==3.4.9 \
--hash=sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380 \
--hash=sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62 \
--hash=sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226 \
--hash=sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b \
--hash=sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501 \
--hash=sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698 \
--hash=sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e \
--hash=sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4 \
--hash=sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519 \
--hash=sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe \
--hash=sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614 \
--hash=sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0 \
--hash=sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0 \
--hash=sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917 \
--hash=sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9 \
--hash=sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32 \
--hash=sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63 \
--hash=sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd \
--hash=sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198 \
--hash=sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde \
--hash=sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012 \
--hash=sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b \
--hash=sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5 \
--hash=sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8 \
--hash=sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35 \
--hash=sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642 \
--hash=sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2 \
--hash=sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9 \
--hash=sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c \
--hash=sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33 \
--hash=sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf \
--hash=sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9 \
--hash=sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84 \
--hash=sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44 \
--hash=sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9 \
--hash=sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177 \
--hash=sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b \
--hash=sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39 \
--hash=sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41 \
--hash=sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0 \
--hash=sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9 \
--hash=sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48 \
--hash=sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046 \
--hash=sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632 \
--hash=sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2 \
--hash=sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1 \
--hash=sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990 \
--hash=sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b \
--hash=sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534 \
--hash=sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81 \
--hash=sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a \
--hash=sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d \
--hash=sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf \
--hash=sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115
# via requests
click==8.4.1 \ click==8.4.1 \
--hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
--hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96 --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
@@ -102,7 +45,6 @@ colorama==0.4.6 ; sys_platform == 'win32' \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via # via
# click # click
# qrcode
# tqdm # tqdm
# uvicorn # uvicorn
distro==1.9.0 \ distro==1.9.0 \
@@ -119,9 +61,6 @@ django==6.0.4 \
# djangorestframework # djangorestframework
# onlinejudge # onlinejudge
# sentry-sdk # sentry-sdk
django-dbconn-retry==0.3.1 \
--hash=sha256:d4b64d915440c3e5902ef8edf836366a6f4c4f027d34902135d7335233d6dbba
# via onlinejudge
django-dramatiq==0.15.0 \ django-dramatiq==0.15.0 \
--hash=sha256:23f0bc418a860952adbf822c4aa3b9c46c51d3d9f50be0a8ed3d19a53380df1d \ --hash=sha256:23f0bc418a860952adbf822c4aa3b9c46c51d3d9f50be0a8ed3d19a53380df1d \
--hash=sha256:e3cf1b2ac288fe4a7aa198c9450fe242ed312df8850f3f9e18ce01b8acc78b96 --hash=sha256:e3cf1b2ac288fe4a7aa198c9450fe242ed312df8850f3f9e18ce01b8acc78b96
@@ -188,14 +127,15 @@ httptools==0.8.0 \
httpx==0.28.1 \ httpx==0.28.1 \
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
# via openai # via
# onlinejudge
# openai
idna==3.13 \ idna==3.13 \
--hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \ --hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \
--hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3 --hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3
# via # via
# anyio # anyio
# httpx # httpx
# requests
jieba==0.42.1 \ jieba==0.42.1 \
--hash=sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2 --hash=sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2
# via onlinejudge # via onlinejudge
@@ -309,10 +249,6 @@ openai==2.34.0 \
--hash=sha256:828b4efcbb126352c2b5eb97d33ae890c92a71ab72511aefc1b7fe64aeccb07b \ --hash=sha256:828b4efcbb126352c2b5eb97d33ae890c92a71ab72511aefc1b7fe64aeccb07b \
--hash=sha256:c996a71b1a210f3569844572ad4c609307e978515fb76877cf449b72596e549e --hash=sha256:c996a71b1a210f3569844572ad4c609307e978515fb76877cf449b72596e549e
# via onlinejudge # via onlinejudge
otpauth==2.2.1 \
--hash=sha256:169a7adbd715fca687f6a66d02ccdbefc229fb49f8a634b958d286f908134d59 \
--hash=sha256:b7eabe0ed91cb67eb3054b7f517e4b4a7495fb30eaf2951897d41c8feef5de73
# via onlinejudge
packaging==26.2 \ packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
@@ -540,20 +476,31 @@ pyyaml==6.0.3 \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
# via uvicorn # via uvicorn
qrcode==8.2 \
--hash=sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f \
--hash=sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c
# via onlinejudge
redis==7.4.0 \ redis==7.4.0 \
--hash=sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad \ --hash=sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad \
--hash=sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec --hash=sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec
# via # via
# channels-redis # channels-redis
# django-redis # django-redis
requests==2.34.2 \ ruff==0.15.12 \
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ --hash=sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b \
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed --hash=sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33 \
# via onlinejudge --hash=sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0 \
--hash=sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002 \
--hash=sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339 \
--hash=sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e \
--hash=sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847 \
--hash=sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f \
--hash=sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6 \
--hash=sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d \
--hash=sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20 \
--hash=sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd \
--hash=sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c \
--hash=sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5 \
--hash=sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6 \
--hash=sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c \
--hash=sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5 \
--hash=sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5
sentry-sdk==2.59.0 \ sentry-sdk==2.59.0 \
--hash=sha256:abcf65ee9a9d9cdebf9ad369782408ecca9c1c792686ef06ba34f5ab233527fe \ --hash=sha256:abcf65ee9a9d9cdebf9ad369782408ecca9c1c792686ef06ba34f5ab233527fe \
--hash=sha256:cd265808ef8bf3f3edf69b527c0a0b2b6b1322762679e55b8987db2e9584aec1 --hash=sha256:cd265808ef8bf3f3edf69b527c0a0b2b6b1322762679e55b8987db2e9584aec1
@@ -643,9 +590,7 @@ tzdata==2026.2 ; sys_platform == 'win32' \
urllib3==2.6.3 \ urllib3==2.6.3 \
--hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \
--hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4
# via # via sentry-sdk
# requests
# sentry-sdk
uvicorn==0.48.0 \ uvicorn==0.48.0 \
--hash=sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad \ --hash=sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad \
--hash=sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37 --hash=sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37

View File

@@ -4,7 +4,7 @@ import logging
from datetime import timedelta from datetime import timedelta
from urllib.parse import urljoin from urllib.parse import urljoin
import requests import httpx
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.models import F from django.db.models import F
from django.utils import timezone from django.utils import timezone
@@ -73,7 +73,8 @@ class DispatcherBase(object):
if data: if data:
kwargs["json"] = data kwargs["json"] = data
try: try:
return requests.post(url, **kwargs).json() # timeout=None 保持与原 requests 实现一致:判题请求是同步等结果的,不能被默认超时打断
return httpx.post(url, timeout=None, **kwargs).json()
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)

View File

@@ -40,7 +40,6 @@ VENDOR_APPS = [
"rest_framework", "rest_framework",
"channels", "channels",
"django_dramatiq", "django_dramatiq",
"django_dbconn_retry",
] ]

View File

@@ -9,20 +9,17 @@ dependencies = [
"channels-redis>=4.3.0,<5", "channels-redis>=4.3.0,<5",
"uvicorn[standard]>=0.34.0,<1", "uvicorn[standard]>=0.34.0,<1",
"django>=6.0.4,<6.1", "django>=6.0.4,<6.1",
"django-dbconn-retry>=0.3.1,<0.4",
"django-dramatiq>=0.15.0,<0.16", "django-dramatiq>=0.15.0,<0.16",
"django-redis>=6.0.0,<7", "django-redis>=6.0.0,<7",
"djangorestframework>=3.17.1,<4", "djangorestframework>=3.17.1,<4",
"dramatiq>=2.1.0,<3", "dramatiq>=2.1.0,<3",
"gunicorn>=26.0.0,<27", "gunicorn>=26.0.0,<27",
"httpx>=0.28.1,<1",
"openai>=2.34.0,<3", "openai>=2.34.0,<3",
"otpauth>=2.2.1,<3",
"pillow>=12.2.0,<13", "pillow>=12.2.0,<13",
"psycopg>=3.3.4,<4", "psycopg>=3.3.4,<4",
"psycopg-binary>=3.3.4,<4", "psycopg-binary>=3.3.4,<4",
"python-dateutil>=2.9.0.post0,<3", "python-dateutil>=2.9.0.post0,<3",
"qrcode>=8.2,<9",
"requests>=2.33.1,<3",
"sentry-sdk[django]>=2.0.0,<3", "sentry-sdk[django]>=2.0.0,<3",
"tree-sitter>=0.25.2", "tree-sitter>=0.25.2",
"tree-sitter-c>=0.24.2", "tree-sitter-c>=0.24.2",

115
uv.lock generated
View File

@@ -70,67 +70,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/fe/b7224a401ad227b263e5ba84753ffb5a88df048f3b15efd2797903543ce4/channels_redis-4.3.0-py3-none-any.whl", hash = "sha256:48f3e902ae2d5fef7080215524f3b4a1d3cea4e304150678f867a1a822c0d9f5", size = 20641, upload-time = "2025-07-22T13:48:44.545Z" }, { url = "https://files.pythonhosted.org/packages/df/fe/b7224a401ad227b263e5ba84753ffb5a88df048f3b15efd2797903543ce4/channels_redis-4.3.0-py3-none-any.whl", hash = "sha256:48f3e902ae2d5fef7080215524f3b4a1d3cea4e304150678f867a1a822c0d9f5", size = 20641, upload-time = "2025-07-22T13:48:44.545Z" },
] ]
[[package]]
name = "charset-normalizer"
version = "3.4.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" },
{ url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" },
{ url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" },
{ url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" },
{ url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" },
{ url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" },
{ url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" },
{ url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" },
{ url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" },
{ url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" },
{ url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" },
{ url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" },
{ url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" },
{ url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" },
{ url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" },
{ url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" },
{ url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" },
{ url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" },
{ url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" },
{ url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" },
{ url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" },
{ url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" },
{ url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" },
{ url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" },
{ url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" },
{ url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" },
{ url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" },
{ url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" },
{ url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" },
{ url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" },
{ url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" },
{ url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" },
{ url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" },
{ url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" },
{ url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" },
{ url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" },
{ url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" },
{ url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" },
{ url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" },
{ url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" },
{ url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" },
{ url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" },
{ url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" },
{ url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" },
{ url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" },
{ url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" },
{ url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" },
{ url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" },
{ url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" },
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.4.1" version = "8.4.1"
@@ -175,14 +114,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/47/3d61d611609764aa71a37f7037b870e7bfb22937366974c4fd46cada7bab/django-6.0.4-py3-none-any.whl", hash = "sha256:14359c809fc16e8f81fd2b59d7d348e4d2d799da6840b10522b6edf7b8afc1da", size = 8368342, upload-time = "2026-04-07T13:55:37.999Z" }, { url = "https://files.pythonhosted.org/packages/e9/47/3d61d611609764aa71a37f7037b870e7bfb22937366974c4fd46cada7bab/django-6.0.4-py3-none-any.whl", hash = "sha256:14359c809fc16e8f81fd2b59d7d348e4d2d799da6840b10522b6edf7b8afc1da", size = 8368342, upload-time = "2026-04-07T13:55:37.999Z" },
] ]
[[package]]
name = "django-dbconn-retry"
version = "0.3.1"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/2a/4ac8cf6fa6ae3f127ac7ecfa4c2c712129b3803978d3fecc4d127024e016/django_dbconn_retry-0.3.1-py3-none-any.whl", hash = "sha256:d4b64d915440c3e5902ef8edf836366a6f4c4f027d34902135d7335233d6dbba", size = 8999, upload-time = "2026-05-02T15:56:58.862Z" },
]
[[package]] [[package]]
name = "django-dramatiq" name = "django-dramatiq"
version = "0.15.0" version = "0.15.0"
@@ -455,21 +386,18 @@ dependencies = [
{ name = "channels" }, { name = "channels" },
{ name = "channels-redis" }, { name = "channels-redis" },
{ name = "django" }, { name = "django" },
{ name = "django-dbconn-retry" },
{ name = "django-dramatiq" }, { name = "django-dramatiq" },
{ name = "django-redis" }, { name = "django-redis" },
{ name = "djangorestframework" }, { name = "djangorestframework" },
{ name = "dramatiq" }, { name = "dramatiq" },
{ name = "gunicorn" }, { name = "gunicorn" },
{ name = "httpx" },
{ name = "jieba" }, { name = "jieba" },
{ name = "openai" }, { name = "openai" },
{ name = "otpauth" },
{ name = "pillow" }, { name = "pillow" },
{ name = "psycopg" }, { name = "psycopg" },
{ name = "psycopg-binary" }, { name = "psycopg-binary" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "qrcode" },
{ name = "requests" },
{ name = "sentry-sdk", extra = ["django"] }, { name = "sentry-sdk", extra = ["django"] },
{ name = "tree-sitter" }, { name = "tree-sitter" },
{ name = "tree-sitter-c" }, { name = "tree-sitter-c" },
@@ -489,21 +417,18 @@ requires-dist = [
{ name = "channels", specifier = ">=4.3.2,<5" }, { name = "channels", specifier = ">=4.3.2,<5" },
{ name = "channels-redis", specifier = ">=4.3.0,<5" }, { name = "channels-redis", specifier = ">=4.3.0,<5" },
{ name = "django", specifier = ">=6.0.4,<6.1" }, { name = "django", specifier = ">=6.0.4,<6.1" },
{ name = "django-dbconn-retry", specifier = ">=0.3.1,<0.4" },
{ name = "django-dramatiq", specifier = ">=0.15.0,<0.16" }, { name = "django-dramatiq", specifier = ">=0.15.0,<0.16" },
{ name = "django-redis", specifier = ">=6.0.0,<7" }, { name = "django-redis", specifier = ">=6.0.0,<7" },
{ name = "djangorestframework", specifier = ">=3.17.1,<4" }, { name = "djangorestframework", specifier = ">=3.17.1,<4" },
{ name = "dramatiq", specifier = ">=2.1.0,<3" }, { name = "dramatiq", specifier = ">=2.1.0,<3" },
{ name = "gunicorn", specifier = ">=26.0.0,<27" }, { name = "gunicorn", specifier = ">=26.0.0,<27" },
{ name = "httpx", specifier = ">=0.28.1,<1" },
{ name = "jieba", specifier = ">=0.42.1" }, { name = "jieba", specifier = ">=0.42.1" },
{ name = "openai", specifier = ">=2.34.0,<3" }, { name = "openai", specifier = ">=2.34.0,<3" },
{ name = "otpauth", specifier = ">=2.2.1,<3" },
{ name = "pillow", specifier = ">=12.2.0,<13" }, { name = "pillow", specifier = ">=12.2.0,<13" },
{ name = "psycopg", specifier = ">=3.3.4,<4" }, { name = "psycopg", specifier = ">=3.3.4,<4" },
{ name = "psycopg-binary", specifier = ">=3.3.4,<4" }, { name = "psycopg-binary", specifier = ">=3.3.4,<4" },
{ name = "python-dateutil", specifier = ">=2.9.0.post0,<3" }, { name = "python-dateutil", specifier = ">=2.9.0.post0,<3" },
{ name = "qrcode", specifier = ">=8.2,<9" },
{ name = "requests", specifier = ">=2.33.1,<3" },
{ name = "sentry-sdk", extras = ["django"], specifier = ">=2.0.0,<3" }, { name = "sentry-sdk", extras = ["django"], specifier = ">=2.0.0,<3" },
{ name = "tree-sitter", specifier = ">=0.25.2" }, { name = "tree-sitter", specifier = ">=0.25.2" },
{ name = "tree-sitter-c", specifier = ">=0.24.2" }, { name = "tree-sitter-c", specifier = ">=0.24.2" },
@@ -534,15 +459,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/40/f090499f10514515081d09cb9da09f25b821eb20497e9423afe4f07b4ecf/openai-2.34.0-py3-none-any.whl", hash = "sha256:c996a71b1a210f3569844572ad4c609307e978515fb76877cf449b72596e549e", size = 1316535, upload-time = "2026-05-04T17:34:06.773Z" }, { url = "https://files.pythonhosted.org/packages/f2/40/f090499f10514515081d09cb9da09f25b821eb20497e9423afe4f07b4ecf/openai-2.34.0-py3-none-any.whl", hash = "sha256:c996a71b1a210f3569844572ad4c609307e978515fb76877cf449b72596e549e", size = 1316535, upload-time = "2026-05-04T17:34:06.773Z" },
] ]
[[package]]
name = "otpauth"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/99/e6c2adda32cc969b0b7c762e17c53575db5a31e4d55e08dee36c0f801573/otpauth-2.2.1.tar.gz", hash = "sha256:169a7adbd715fca687f6a66d02ccdbefc229fb49f8a634b958d286f908134d59", size = 26444, upload-time = "2025-05-09T11:12:01.635Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/2c/bc109eade0af850d38837b9835f812d8a8bcad1269bbcbdc136deb9fed30/otpauth-2.2.1-py3-none-any.whl", hash = "sha256:b7eabe0ed91cb67eb3054b7f517e4b4a7495fb30eaf2951897d41c8feef5de73", size = 6412, upload-time = "2025-05-09T11:12:00.485Z" },
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.2" version = "26.2"
@@ -831,18 +747,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
] ]
[[package]]
name = "qrcode"
version = "8.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" },
]
[[package]] [[package]]
name = "redis" name = "redis"
version = "7.4.0" version = "7.4.0"
@@ -852,21 +756,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" },
] ]
[[package]]
name = "requests"
version = "2.34.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.12" version = "0.15.12"