feat(production): Configure WebSocket auth and multi-tenant cookies for production

- Add TokenAuthMiddleware to WebSocket connections for authenticated access
- Configure SESSION_COOKIE_DOMAIN and CSRF_COOKIE_DOMAIN for subdomain sharing (.smoothschedule.com)
- Remove '/api' prefix from URL routes to align frontend/backend conventions
- Fix imports in asgi.py (tickets instead of smoothschedule.tickets)
- Update dependencies (pyproject.toml, uv.lock)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-01 02:54:03 -05:00
parent 2b4104a819
commit 99adeda83c
7 changed files with 269 additions and 54 deletions

View File

@@ -22,7 +22,7 @@ export default defineConfig({
/* Shared settings for all the projects below */ /* Shared settings for all the projects below */
use: { use: {
/* Base URL for all tests */ /* Base URL for all tests */
baseURL: 'http://lvh.me:5174', baseURL: 'http://lvh.me:5173',
/* Collect trace when retrying the failed test */ /* Collect trace when retrying the failed test */
trace: 'on-first-retry', trace: 'on-first-retry',
@@ -52,7 +52,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: 'npm run dev', command: 'npm run dev',
url: 'http://lvh.me:5174', url: 'http://lvh.me:5173',
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },
}); });

View File

@@ -10,7 +10,8 @@ django_asgi_app = get_asgi_application()
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from smoothschedule.tickets import routing as tickets_routing # Assuming we'll have tickets routing from tickets import routing as tickets_routing # Assuming we'll have tickets routing
from tickets.middleware import TokenAuthMiddleware
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
@@ -18,9 +19,11 @@ application = ProtocolTypeRouter(
"http": django_asgi_app, "http": django_asgi_app,
# Just HTTP for now. (We can add other protocols later.) # Just HTTP for now. (We can add other protocols later.)
"websocket": AuthMiddlewareStack( "websocket": AuthMiddlewareStack(
TokenAuthMiddleware(
URLRouter( URLRouter(
tickets_routing.websocket_urlpatterns # Include ticket-specific WebSocket routes tickets_routing.websocket_urlpatterns # Include ticket-specific WebSocket routes
) )
)
), ),
} }
) )

View File

@@ -46,10 +46,14 @@ SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-name # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-name
SESSION_COOKIE_NAME = "__Secure-sessionid" SESSION_COOKIE_NAME = "__Secure-sessionid"
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-domain
SESSION_COOKIE_DOMAIN = ".smoothschedule.com"
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
CSRF_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-name # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-name
CSRF_COOKIE_NAME = "__Secure-csrftoken" CSRF_COOKIE_NAME = "__Secure-csrftoken"
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-domain
CSRF_COOKIE_DOMAIN = ".smoothschedule.com"
# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
# TODO: set this to 60 seconds first and then to 518400 once you prove the former works # TODO: set this to 60 seconds first and then to 518400 once you prove the former works

View File

@@ -48,74 +48,74 @@ urlpatterns = [
# API URLS # API URLS
urlpatterns += [ urlpatterns += [
# Stripe Webhooks (dj-stripe built-in handler) # Stripe Webhooks (dj-stripe built-in handler)
path("api/stripe/", include("djstripe.urls", namespace="djstripe")), path("stripe/", include("djstripe.urls", namespace="djstripe")),
# Public API v1 (for third-party integrations) # Public API v1 (for third-party integrations)
path("api/v1/", include("smoothschedule.public_api.urls", namespace="public_api")), path("v1/", include("smoothschedule.public_api.urls", namespace="public_api")),
# Schedule API (internal) # Schedule API (internal)
path("api/", include("schedule.urls")), path("", include("schedule.urls")),
# Payments API # Payments API
path("api/payments/", include("payments.urls")), path("payments/", include("payments.urls")),
# Tickets API # Tickets API
path("tickets/", include("tickets.urls")), path("tickets/", include("tickets.urls")),
# Notifications API # Notifications API
path("api/notifications/", include("notifications.urls")), path("notifications/", include("notifications.urls")),
# Platform API # Platform API
path("api/platform/", include("platform_admin.urls", namespace="platform")), path("platform/", include("platform_admin.urls", namespace="platform")),
# OAuth Email Integration API # OAuth Email Integration API
path("api/oauth/", include("core.oauth_urls", namespace="oauth")), path("oauth/", include("core.oauth_urls", namespace="oauth")),
path("api/auth/oauth/", include("core.oauth_urls", namespace="auth_oauth")), path("auth/oauth/", include("core.oauth_urls", namespace="auth_oauth")),
# Auth API # Auth API
path("api/auth-token/", csrf_exempt(obtain_auth_token), name="obtain_auth_token"), path("auth-token/", csrf_exempt(obtain_auth_token), name="obtain_auth_token"),
path("auth/signup/check-subdomain/", check_subdomain_view, name="check_subdomain"), path("auth/signup/check-subdomain/", check_subdomain_view, name="check_subdomain"),
path("auth/signup/", signup_view, name="signup"), path("auth/signup/", signup_view, name="signup"),
path("api/auth/login/", login_view, name="login"), path("auth/login/", login_view, name="login"),
path("api/auth/me/", current_user_view, name="current_user"), path("auth/me/", current_user_view, name="current_user"),
path("api/auth/logout/", logout_view, name="logout"), path("auth/logout/", logout_view, name="logout"),
path("api/auth/email/verify/send/", send_verification_email, name="send_verification_email"), path("auth/email/verify/send/", send_verification_email, name="send_verification_email"),
path("api/auth/email/verify/", verify_email, name="verify_email"), path("auth/email/verify/", verify_email, name="verify_email"),
# Hijack (masquerade) API # Hijack (masquerade) API
path("api/auth/hijack/acquire/", hijack_acquire_view, name="hijack_acquire"), path("auth/hijack/acquire/", hijack_acquire_view, name="hijack_acquire"),
path("api/auth/hijack/release/", hijack_release_view, name="hijack_release"), path("auth/hijack/release/", hijack_release_view, name="hijack_release"),
# Staff Invitations API # Staff Invitations API
path("api/staff/invitations/", staff_invitations_view, name="staff_invitations"), path("staff/invitations/", staff_invitations_view, name="staff_invitations"),
path("api/staff/invitations/<int:invitation_id>/", cancel_invitation_view, name="cancel_invitation"), path("staff/invitations/<int:invitation_id>/", cancel_invitation_view, name="cancel_invitation"),
path("api/staff/invitations/<int:invitation_id>/resend/", resend_invitation_view, name="resend_invitation"), path("staff/invitations/<int:invitation_id>/resend/", resend_invitation_view, name="resend_invitation"),
path("api/staff/invitations/token/<str:token>/", invitation_details_view, name="invitation_details"), path("staff/invitations/token/<str:token>/", invitation_details_view, name="invitation_details"),
path("api/staff/invitations/token/<str:token>/accept/", accept_invitation_view, name="accept_invitation"), path("staff/invitations/token/<str:token>/accept/", accept_invitation_view, name="accept_invitation"),
path("api/staff/invitations/token/<str:token>/decline/", decline_invitation_view, name="decline_invitation"), path("staff/invitations/token/<str:token>/decline/", decline_invitation_view, name="decline_invitation"),
# Business API # Business API
path("api/business/current/", current_business_view, name="current_business"), path("business/current/", current_business_view, name="current_business"),
path("api/business/current/update/", update_business_view, name="update_business"), path("business/current/update/", update_business_view, name="update_business"),
path("api/business/oauth-settings/", oauth_settings_view, name="oauth_settings"), path("business/oauth-settings/", oauth_settings_view, name="oauth_settings"),
path("api/business/oauth-credentials/", oauth_credentials_view, name="oauth_credentials"), path("business/oauth-credentials/", oauth_credentials_view, name="oauth_credentials"),
# Custom Domains API # Custom Domains API
path("api/business/domains/", custom_domains_view, name="custom_domains"), path("business/domains/", custom_domains_view, name="custom_domains"),
path("api/business/domains/<int:domain_id>/", custom_domain_detail_view, name="custom_domain_detail"), path("business/domains/<int:domain_id>/", custom_domain_detail_view, name="custom_domain_detail"),
path("api/business/domains/<int:domain_id>/verify/", custom_domain_verify_view, name="custom_domain_verify"), path("business/domains/<int:domain_id>/verify/", custom_domain_verify_view, name="custom_domain_verify"),
path("api/business/domains/<int:domain_id>/set-primary/", custom_domain_set_primary_view, name="custom_domain_set_primary"), path("business/domains/<int:domain_id>/set-primary/", custom_domain_set_primary_view, name="custom_domain_set_primary"),
# Sandbox Mode API # Sandbox Mode API
path("api/sandbox/status/", sandbox_status_view, name="sandbox_status"), path("sandbox/status/", sandbox_status_view, name="sandbox_status"),
path("api/sandbox/toggle/", sandbox_toggle_view, name="sandbox_toggle"), path("sandbox/toggle/", sandbox_toggle_view, name="sandbox_toggle"),
path("api/sandbox/reset/", sandbox_reset_view, name="sandbox_reset"), path("sandbox/reset/", sandbox_reset_view, name="sandbox_reset"),
# MFA (Two-Factor Authentication) API # MFA (Two-Factor Authentication) API
path("api/auth/mfa/status/", mfa_status, name="mfa_status"), path("auth/mfa/status/", mfa_status, name="mfa_status"),
path("api/auth/mfa/phone/send/", send_phone_verification, name="mfa_phone_send"), path("auth/mfa/phone/send/", send_phone_verification, name="mfa_phone_send"),
path("api/auth/mfa/phone/verify/", verify_phone, name="mfa_phone_verify"), path("auth/mfa/phone/verify/", verify_phone, name="mfa_phone_verify"),
path("api/auth/mfa/sms/enable/", enable_sms_mfa, name="mfa_sms_enable"), path("auth/mfa/sms/enable/", enable_sms_mfa, name="mfa_sms_enable"),
path("api/auth/mfa/totp/setup/", setup_totp, name="mfa_totp_setup"), path("auth/mfa/totp/setup/", setup_totp, name="mfa_totp_setup"),
path("api/auth/mfa/totp/verify/", verify_totp_setup, name="mfa_totp_verify"), path("auth/mfa/totp/verify/", verify_totp_setup, name="mfa_totp_verify"),
path("api/auth/mfa/backup-codes/", generate_backup_codes, name="mfa_backup_codes"), path("auth/mfa/backup-codes/", generate_backup_codes, name="mfa_backup_codes"),
path("api/auth/mfa/backup-codes/status/", backup_codes_status, name="mfa_backup_codes_status"), path("auth/mfa/backup-codes/status/", backup_codes_status, name="mfa_backup_codes_status"),
path("api/auth/mfa/disable/", disable_mfa, name="mfa_disable"), path("auth/mfa/disable/", disable_mfa, name="mfa_disable"),
path("api/auth/mfa/login/send/", mfa_login_send_code, name="mfa_login_send"), path("auth/mfa/login/send/", mfa_login_send_code, name="mfa_login_send"),
path("api/auth/mfa/login/verify/", mfa_login_verify, name="mfa_login_verify"), path("auth/mfa/login/verify/", mfa_login_verify, name="mfa_login_verify"),
path("api/auth/mfa/devices/", list_trusted_devices, name="mfa_devices_list"), path("auth/mfa/devices/", list_trusted_devices, name="mfa_devices_list"),
path("api/auth/mfa/devices/<int:device_id>/", revoke_trusted_device, name="mfa_device_revoke"), path("auth/mfa/devices/<int:device_id>/", revoke_trusted_device, name="mfa_device_revoke"),
path("api/auth/mfa/devices/revoke-all/", revoke_all_trusted_devices, name="mfa_devices_revoke_all"), path("auth/mfa/devices/revoke-all/", revoke_all_trusted_devices, name="mfa_devices_revoke_all"),
# API Docs # API Docs
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"), path("schema/", SpectacularAPIView.as_view(), name="api-schema"),
path( path(
"api/docs/", "docs/",
SpectacularSwaggerView.as_view(url_name="api-schema"), SpectacularSwaggerView.as_view(url_name="api-schema"),
name="api-docs", name="api-docs",
), ),

View File

@@ -170,6 +170,7 @@ dependencies = [
"celery==5.5.3", "celery==5.5.3",
"channels==4.0.0", "channels==4.0.0",
"channels-redis==4.1.0", "channels-redis==4.1.0",
"daphne==4.1.2",
"crispy-bootstrap5==2025.6", "crispy-bootstrap5==2025.6",
"django==5.2.8", "django==5.2.8",
"django-allauth[mfa]==65.13.1", "django-allauth[mfa]==65.13.1",

View File

@@ -8,6 +8,7 @@ from .serializers import TicketSerializer, TicketCommentSerializer # Import your
class BaseConsumer(AsyncWebsocketConsumer): class BaseConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
print(f"WebSocket Connect: User={self.scope['user']}, Auth={self.scope['user'].is_authenticated}")
if self.scope["user"].is_authenticated: if self.scope["user"].is_authenticated:
# Add user to a group for their tenant # Add user to a group for their tenant
if hasattr(self.scope["user"], 'tenant') and self.scope["user"].tenant: if hasattr(self.scope["user"], 'tenant') and self.scope["user"].tenant:

206
smoothschedule/uv.lock generated
View File

@@ -163,6 +163,37 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
] ]
[[package]]
name = "autobahn"
version = "25.11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cbor2" },
{ name = "cryptography" },
{ name = "hyperlink" },
{ name = "msgpack", marker = "platform_python_implementation == 'CPython'" },
{ name = "py-ubjson" },
{ name = "txaio" },
{ name = "u-msgpack-python", marker = "platform_python_implementation != 'CPython'" },
{ name = "ujson" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/aab5632fbd88ed26d330ab156af80c9a90419dfa2841296fd556a65e5fac/autobahn-25.11.1.tar.gz", hash = "sha256:52e62b9cc80c3e989b182952a60fd25c9a69afb00854a925a2b185f7b1f73cf1", size = 447019, upload-time = "2025-11-24T08:31:27.919Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9e/ff60f0c88729811ca66bea4f293e03460e0a28870cc2721e47792c184fb0/autobahn-25.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:78910e9d8393198e7c87d072a4d22c52110422294aff90aa9ab5c253e336312f", size = 615347, upload-time = "2025-11-24T08:31:10.112Z" },
{ url = "https://files.pythonhosted.org/packages/1d/bc/997418f7c9a29ddee8d5a8391da9cba1aa49fffdbb4332702b7fee3b757c/autobahn-25.11.1-cp313-cp313-manylinux1_x86_64.manylinux_2_34_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:966bfd82669005c2b12872dfd556ac76e23d73438dc9193136b89263511d9245", size = 668946, upload-time = "2025-11-24T08:31:11.503Z" },
{ url = "https://files.pythonhosted.org/packages/00/d1/6d3540714e4770a18ffdd1f04f15f07df3f802044064d2465b746c0dfeba/autobahn-25.11.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d7e26d9f3bba13b2e3f5dcc422be603b913df5dfaef1758d78cd0fd04b36249", size = 644586, upload-time = "2025-11-24T08:31:13.129Z" },
{ url = "https://files.pythonhosted.org/packages/2b/1a/03233afd8c3196a7b2510c7f103deb8fe06b5384683477c39a1bd6e4a573/autobahn-25.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:e6171e02ed7f70436c17cdfb12fc6ac9b20cd2a9c8e3993b192d1c9d891f5cc5", size = 625171, upload-time = "2025-11-24T08:31:14.785Z" },
]
[[package]]
name = "automat"
version = "25.4.16"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/0f/d40bbe294bbf004d436a8bcbcfaadca8b5140d39ad0ad3d73d1a8ba15f14/automat-25.4.16.tar.gz", hash = "sha256:0017591a5477066e90d26b0e696ddc143baafd87b588cfac8100bc6be9634de0", size = 129977, upload-time = "2025-04-16T20:12:16.002Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/ff/1175b0b7371e46244032d43a56862d0af455823b5280a50c63d99cc50f18/automat-25.4.16-py3-none-any.whl", hash = "sha256:04e9bce696a8d5671ee698005af6e5a9fa15354140a87f4870744604dcdd3ba1", size = 42842, upload-time = "2025-04-16T20:12:14.447Z" },
]
[[package]] [[package]]
name = "babel" name = "babel"
version = "2.17.0" version = "2.17.0"
@@ -218,6 +249,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" },
] ]
[[package]]
name = "cbor2"
version = "5.7.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/b8/c0f6a7d46f816cb18b1fda61a2fe648abe16039f1ff93ea720a6e9fb3cee/cbor2-5.7.1.tar.gz", hash = "sha256:7a405a1d7c8230ee9acf240aad48ae947ef584e8af05f169f3c1bde8f01f8b71", size = 102467, upload-time = "2025-10-24T09:23:06.569Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/b1/51fb868fe38d893c570bb90b38d365ff0f00421402c1ae8f63b31b25d665/cbor2-5.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:59d5da59fffe89692d5bd1530eef4d26e4eb7aa794aaa1f4e192614786409009", size = 69068, upload-time = "2025-10-24T09:22:34.464Z" },
{ url = "https://files.pythonhosted.org/packages/b9/db/5abc62ec456f552f617aac3359a5d7114b23be9c4d886169592cd5f074b9/cbor2-5.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:533117918d518e01348f8cd0331271c207e7224b9a1ed492a0ff00847f28edc8", size = 68927, upload-time = "2025-10-24T09:22:35.458Z" },
{ url = "https://files.pythonhosted.org/packages/9a/c2/58d787395c99874d2a2395b3a22c9d48a3cfc5a7dcd5817bf74764998b75/cbor2-5.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d6d9436ff3c3323ea5863ecf7ae1139590991685b44b9eb6b7bb1734a594af6", size = 285185, upload-time = "2025-10-24T09:22:36.867Z" },
{ url = "https://files.pythonhosted.org/packages/d0/9c/b680b264a8f4b9aa59c95e166c816275a13138cbee92dd2917f58bca47b9/cbor2-5.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:661b871ca754a619fcd98c13a38b4696b2b57dab8b24235c00b0ba322c040d24", size = 284440, upload-time = "2025-10-24T09:22:38.08Z" },
{ url = "https://files.pythonhosted.org/packages/1f/59/68183c655d6226d0eee10027f52516882837802a8d5746317a88362ed686/cbor2-5.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8065aa90d715fd9bb28727b2d774ee16e695a0e1627ae76e54bf19f9d99d63f", size = 276876, upload-time = "2025-10-24T09:22:39.561Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a2/1964e0a569d2b81e8f4862753fee7701ae5773c22e45492a26f92f62e75a/cbor2-5.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb1b7047d73590cfe8e373e2c804fa99be47e55b1b6186602d0f86f384cecec1", size = 278216, upload-time = "2025-10-24T09:22:41.132Z" },
{ url = "https://files.pythonhosted.org/packages/00/78/9b566d68cb88bb1ecebe354765625161c9d6060a16e55008006d6359f776/cbor2-5.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:31d511df7ebd6624fdb4cecdafb4ffb9a205f9ff8c8d98edd1bef0d27f944d74", size = 68451, upload-time = "2025-10-24T09:22:42.227Z" },
{ url = "https://files.pythonhosted.org/packages/db/85/7a6a922d147d027fd5d8fd5224b39e8eaf152a42e8cf16351458096d3d62/cbor2-5.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:f5d37f7b0f84394d2995bd8722cb01c86a885c4821a864a34b7b4d9950c5e26e", size = 64111, upload-time = "2025-10-24T09:22:43.213Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7d/383bafeabb54c17fe5b6d5aca4e863e6b7df10bcc833b34aa169e9dfce1a/cbor2-5.7.1-py3-none-any.whl", hash = "sha256:68834e4eff2f56629ce6422b0634bc3f74c5a4269de5363f5265fe452c706ba7", size = 23829, upload-time = "2025-10-24T09:23:05.54Z" },
]
[[package]] [[package]]
name = "celery" name = "celery"
version = "5.5.3" version = "5.5.3"
@@ -389,6 +437,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
] ]
[[package]]
name = "constantly"
version = "23.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4d/6f/cb2a94494ff74aa9528a36c5b1422756330a75a8367bf20bd63171fc324d/constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd", size = 13300, upload-time = "2023-10-28T23:18:24.316Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z" },
]
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.12.0" version = "7.12.0"
@@ -504,6 +561,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/51/ef6c5628e46092f0a54c7cee69acc827adc6b6aab57b55d344fefbdf28f1/cssbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:78c84d5e5378df7d08622bbd0477a1abdbd209680e95480bf22f12d5701efc98", size = 123667, upload-time = "2025-02-27T17:53:43.594Z" }, { url = "https://files.pythonhosted.org/packages/63/51/ef6c5628e46092f0a54c7cee69acc827adc6b6aab57b55d344fefbdf28f1/cssbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:78c84d5e5378df7d08622bbd0477a1abdbd209680e95480bf22f12d5701efc98", size = 123667, upload-time = "2025-02-27T17:53:43.594Z" },
] ]
[[package]]
name = "daphne"
version = "4.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "autobahn" },
{ name = "twisted", extra = ["tls"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/1a/c1/aedf180beb12395835cba791ce7239b8880009d9d37564d72b7590cde605/daphne-4.1.2.tar.gz", hash = "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761", size = 37882, upload-time = "2024-04-11T13:32:34.594Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/d6/466f9219281472ecc269ab1d351c5b22a3cfca2d52f72881917949e414df/daphne-4.1.2-py3-none-any.whl", hash = "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a", size = 30940, upload-time = "2024-04-11T13:32:32.634Z" },
]
[[package]] [[package]]
name = "decorator" name = "decorator"
version = "5.2.1" version = "5.2.1"
@@ -1098,6 +1169,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/5b/9512c5fb6c8218332b530f13500c6ff5f3ce3342f35e0dd7be9ac3856fd3/humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff", size = 132092, upload-time = "2025-10-15T13:04:49.404Z" }, { url = "https://files.pythonhosted.org/packages/c3/5b/9512c5fb6c8218332b530f13500c6ff5f3ce3342f35e0dd7be9ac3856fd3/humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff", size = 132092, upload-time = "2025-10-15T13:04:49.404Z" },
] ]
[[package]]
name = "hyperlink"
version = "21.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", size = 140743, upload-time = "2021-01-08T05:51:20.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4", size = 74638, upload-time = "2021-01-08T05:51:22.906Z" },
]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.15" version = "2.6.15"
@@ -1125,6 +1208,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
] ]
[[package]]
name = "incremental"
version = "24.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ef/3c/82e84109e02c492f382c711c58a3dd91badda6d746def81a1465f74dc9f5/incremental-24.11.0.tar.gz", hash = "sha256:87d3480dbb083c1d736222511a8cf380012a8176c2456d01ef483242abbbcf8c", size = 24000, upload-time = "2025-11-28T02:30:17.861Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/55/0f4df2a44053867ea9cbea73fc588b03c55605cd695cee0a3d86f0029cb2/incremental-24.11.0-py3-none-any.whl", hash = "sha256:a34450716b1c4341fe6676a0598e88a39e04189f4dce5dc96f656e040baa10b3", size = 21109, upload-time = "2025-11-28T02:30:16.442Z" },
]
[[package]] [[package]]
name = "inflection" name = "inflection"
version = "0.5.1" version = "0.5.1"
@@ -1677,6 +1772,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
] ]
[[package]]
name = "py-ubjson"
version = "0.16.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1d/c7/28220d37e041fe1df03e857fe48f768dcd30cd151480bf6f00da8713214a/py-ubjson-0.16.1.tar.gz", hash = "sha256:b9bfb8695a1c7e3632e800fb83c943bf67ed45ddd87cd0344851610c69a5a482", size = 50316, upload-time = "2020-04-18T15:05:57.698Z" }
[[package]] [[package]]
name = "pyasn1" name = "pyasn1"
version = "0.6.1" version = "0.6.1"
@@ -1742,6 +1843,18 @@ crypto = [
{ name = "cryptography" }, { name = "cryptography" },
] ]
[[package]]
name = "pyopenssl"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "9.0.1" version = "9.0.1"
@@ -2050,6 +2163,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/b6/ce7c502a366f4835b1f9c057753f6989a92d3c70cbadb168193f5fb7499b/sentry_sdk-2.46.0-py2.py3-none-any.whl", hash = "sha256:4eeeb60198074dff8d066ea153fa6f241fef1668c10900ea53a4200abc8da9b1", size = 406266, upload-time = "2025-11-24T09:34:12.114Z" }, { url = "https://files.pythonhosted.org/packages/4b/b6/ce7c502a366f4835b1f9c057753f6989a92d3c70cbadb168193f5fb7499b/sentry_sdk-2.46.0-py2.py3-none-any.whl", hash = "sha256:4eeeb60198074dff8d066ea153fa6f241fef1668c10900ea53a4200abc8da9b1", size = 406266, upload-time = "2025-11-24T09:34:12.114Z" },
] ]
[[package]]
name = "service-identity"
version = "24.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "cryptography" },
{ name = "pyasn1" },
{ name = "pyasn1-modules" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/a5/dfc752b979067947261dbbf2543470c58efe735c3c1301dd870ef27830ee/service_identity-24.2.0.tar.gz", hash = "sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09", size = 39245, upload-time = "2024-10-26T07:21:57.736Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/2c/ca6dd598b384bc1ce581e24aaae0f2bed4ccac57749d5c3befbb5e742081/service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85", size = 11364, upload-time = "2024-10-26T07:21:56.302Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
@@ -2069,6 +2197,7 @@ dependencies = [
{ name = "channels" }, { name = "channels" },
{ name = "channels-redis" }, { name = "channels-redis" },
{ name = "crispy-bootstrap5" }, { name = "crispy-bootstrap5" },
{ name = "daphne" },
{ name = "dj-stripe" }, { name = "dj-stripe" },
{ name = "django" }, { name = "django" },
{ name = "django-allauth", extra = ["mfa"] }, { name = "django-allauth", extra = ["mfa"] },
@@ -2134,6 +2263,7 @@ requires-dist = [
{ name = "channels", specifier = "==4.0.0" }, { name = "channels", specifier = "==4.0.0" },
{ name = "channels-redis", specifier = "==4.1.0" }, { name = "channels-redis", specifier = "==4.1.0" },
{ name = "crispy-bootstrap5", specifier = "==2025.6" }, { name = "crispy-bootstrap5", specifier = "==2025.6" },
{ name = "daphne", specifier = "==4.1.2" },
{ name = "dj-stripe", specifier = ">=2.9.0" }, { name = "dj-stripe", specifier = ">=2.9.0" },
{ name = "django", specifier = "==5.2.8" }, { name = "django", specifier = "==5.2.8" },
{ name = "django-allauth", extras = ["mfa"], specifier = "==65.13.1" }, { name = "django-allauth", extras = ["mfa"], specifier = "==65.13.1" },
@@ -2430,6 +2560,40 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/cd/5b18437b7a2e1d69d9a8e6196c8ddb6f42b62a701cb127f8d394a7200761/twilio-9.8.7-py2.py3-none-any.whl", hash = "sha256:ef07646dde58e45f24089ab90e92507ad1167e34a5f798e4d73f322e1348216b", size = 1838762, upload-time = "2025-11-20T04:36:11.351Z" }, { url = "https://files.pythonhosted.org/packages/c6/cd/5b18437b7a2e1d69d9a8e6196c8ddb6f42b62a701cb127f8d394a7200761/twilio-9.8.7-py2.py3-none-any.whl", hash = "sha256:ef07646dde58e45f24089ab90e92507ad1167e34a5f798e4d73f322e1348216b", size = 1838762, upload-time = "2025-11-20T04:36:11.351Z" },
] ]
[[package]]
name = "twisted"
version = "25.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "automat" },
{ name = "constantly" },
{ name = "hyperlink" },
{ name = "incremental" },
{ name = "typing-extensions" },
{ name = "zope-interface" },
]
sdist = { url = "https://files.pythonhosted.org/packages/13/0f/82716ed849bf7ea4984c21385597c949944f0f9b428b5710f79d0afc084d/twisted-25.5.0.tar.gz", hash = "sha256:1deb272358cb6be1e3e8fc6f9c8b36f78eb0fa7c2233d2dbe11ec6fee04ea316", size = 3545725, upload-time = "2025-06-07T09:52:24.858Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/66/ab7efd8941f0bc7b2bd555b0f0471bff77df4c88e0cc31120c82737fec77/twisted-25.5.0-py3-none-any.whl", hash = "sha256:8559f654d01a54a8c3efe66d533d43f383531ebf8d81d9f9ab4769d91ca15df7", size = 3204767, upload-time = "2025-06-07T09:52:21.428Z" },
]
[package.optional-dependencies]
tls = [
{ name = "idna" },
{ name = "pyopenssl" },
{ name = "service-identity" },
]
[[package]]
name = "txaio"
version = "25.9.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2b/20/2e7ccea9ab2dd824d0bd421d9364424afde3bb33863afb80cd9180335019/txaio-25.9.2.tar.gz", hash = "sha256:e42004a077c02eb5819ff004a4989e49db113836708430d59cb13d31bd309099", size = 50008, upload-time = "2025-09-25T22:21:07.958Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/2c/e276b80f73fc0411cefa1c1eeae6bc17955197a9c3e2b41b41f957322549/txaio-25.9.2-py3-none-any.whl", hash = "sha256:a23ce6e627d130e9b795cbdd46c9eaf8abd35e42d2401bb3fea63d38beda0991", size = 31293, upload-time = "2025-09-25T22:21:06.394Z" },
]
[[package]] [[package]]
name = "types-pyyaml" name = "types-pyyaml"
version = "6.0.12.20250915" version = "6.0.12.20250915"
@@ -2469,6 +2633,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
] ]
[[package]]
name = "u-msgpack-python"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/36/9d/a40411a475e7d4838994b7f6bcc6bfca9acc5b119ce3a7503608c4428b49/u-msgpack-python-2.8.0.tar.gz", hash = "sha256:b801a83d6ed75e6df41e44518b4f2a9c221dc2da4bcd5380e3a0feda520bc61a", size = 18167, upload-time = "2023-05-18T09:28:12.187Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/5e/512aeb40fd819f4660d00f96f5c7371ee36fc8c6b605128c5ee59e0b28c6/u_msgpack_python-2.8.0-py2.py3-none-any.whl", hash = "sha256:1d853d33e78b72c4228a2025b4db28cda81214076e5b0422ed0ae1b1b2bb586a", size = 10590, upload-time = "2023-05-18T09:28:10.323Z" },
]
[[package]]
name = "ujson"
version = "5.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/d9/3f17e3c5773fb4941c68d9a37a47b1a79c9649d6c56aefbed87cc409d18a/ujson-5.11.0.tar.gz", hash = "sha256:e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0", size = 7156583, upload-time = "2025-08-20T11:57:02.452Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/ec/2de9dd371d52c377abc05d2b725645326c4562fc87296a8907c7bcdf2db7/ujson-5.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:109f59885041b14ee9569bf0bb3f98579c3fa0652317b355669939e5fc5ede53", size = 55435, upload-time = "2025-08-20T11:55:50.243Z" },
{ url = "https://files.pythonhosted.org/packages/5b/a4/f611f816eac3a581d8a4372f6967c3ed41eddbae4008d1d77f223f1a4e0a/ujson-5.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a31c6b8004438e8c20fc55ac1c0e07dad42941db24176fe9acf2815971f8e752", size = 53193, upload-time = "2025-08-20T11:55:51.373Z" },
{ url = "https://files.pythonhosted.org/packages/e9/c5/c161940967184de96f5cbbbcce45b562a4bf851d60f4c677704b1770136d/ujson-5.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78c684fb21255b9b90320ba7e199780f653e03f6c2528663768965f4126a5b50", size = 57603, upload-time = "2025-08-20T11:55:52.583Z" },
{ url = "https://files.pythonhosted.org/packages/2b/d6/c7b2444238f5b2e2d0e3dab300b9ddc3606e4b1f0e4bed5a48157cebc792/ujson-5.11.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:4c9f5d6a27d035dd90a146f7761c2272cf7103de5127c9ab9c4cd39ea61e878a", size = 59794, upload-time = "2025-08-20T11:55:53.69Z" },
{ url = "https://files.pythonhosted.org/packages/fe/a3/292551f936d3d02d9af148f53e1bc04306b00a7cf1fcbb86fa0d1c887242/ujson-5.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:837da4d27fed5fdc1b630bd18f519744b23a0b5ada1bbde1a36ba463f2900c03", size = 57363, upload-time = "2025-08-20T11:55:54.843Z" },
{ url = "https://files.pythonhosted.org/packages/90/a6/82cfa70448831b1a9e73f882225980b5c689bf539ec6400b31656a60ea46/ujson-5.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:787aff4a84da301b7f3bac09bc696e2e5670df829c6f8ecf39916b4e7e24e701", size = 1036311, upload-time = "2025-08-20T11:55:56.197Z" },
{ url = "https://files.pythonhosted.org/packages/84/5c/96e2266be50f21e9b27acaee8ca8f23ea0b85cb998c33d4f53147687839b/ujson-5.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6dd703c3e86dc6f7044c5ac0b3ae079ed96bf297974598116aa5fb7f655c3a60", size = 1195783, upload-time = "2025-08-20T11:55:58.081Z" },
{ url = "https://files.pythonhosted.org/packages/8d/20/78abe3d808cf3bb3e76f71fca46cd208317bf461c905d79f0d26b9df20f1/ujson-5.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3772e4fe6b0c1e025ba3c50841a0ca4786825a4894c8411bf8d3afe3a8061328", size = 1088822, upload-time = "2025-08-20T11:55:59.469Z" },
{ url = "https://files.pythonhosted.org/packages/d8/50/8856e24bec5e2fc7f775d867aeb7a3f137359356200ac44658f1f2c834b2/ujson-5.11.0-cp313-cp313-win32.whl", hash = "sha256:8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241", size = 39753, upload-time = "2025-08-20T11:56:01.345Z" },
{ url = "https://files.pythonhosted.org/packages/5b/d8/1baee0f4179a4d0f5ce086832147b6cc9b7731c24ca08e14a3fdb8d39c32/ujson-5.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0", size = 43866, upload-time = "2025-08-20T11:56:02.552Z" },
{ url = "https://files.pythonhosted.org/packages/a9/8c/6d85ef5be82c6d66adced3ec5ef23353ed710a11f70b0b6a836878396334/ujson-5.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9", size = 38363, upload-time = "2025-08-20T11:56:03.688Z" },
]
[[package]] [[package]]
name = "uritemplate" name = "uritemplate"
version = "4.2.0" version = "4.2.0"
@@ -2678,3 +2870,17 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" },
{ url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" },
] ]
[[package]]
name = "zope-interface"
version = "8.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/71/c9/5ec8679a04d37c797d343f650c51ad67d178f0001c363e44b6ac5f97a9da/zope_interface-8.1.1.tar.gz", hash = "sha256:51b10e6e8e238d719636a401f44f1e366146912407b58453936b781a19be19ec", size = 254748, upload-time = "2025-11-15T08:32:52.404Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/81/3c3b5386ce4fba4612fd82ffb8a90d76bcfea33ca2b6399f21e94d38484f/zope_interface-8.1.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:84f9be6d959640de9da5d14ac1f6a89148b16da766e88db37ed17e936160b0b1", size = 209046, upload-time = "2025-11-15T08:37:01.473Z" },
{ url = "https://files.pythonhosted.org/packages/4a/e3/32b7cb950c4c4326b3760a8e28e5d6f70ad15f852bfd8f9364b58634f74b/zope_interface-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:531fba91dcb97538f70cf4642a19d6574269460274e3f6004bba6fe684449c51", size = 209104, upload-time = "2025-11-15T08:37:02.887Z" },
{ url = "https://files.pythonhosted.org/packages/a3/3d/c4c68e1752a5f5effa2c1f5eaa4fea4399433c9b058fb7000a34bfb1c447/zope_interface-8.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:fc65f5633d5a9583ee8d88d1f5de6b46cd42c62e47757cfe86be36fb7c8c4c9b", size = 259277, upload-time = "2025-11-15T08:37:04.389Z" },
{ url = "https://files.pythonhosted.org/packages/fd/5b/cf4437b174af7591ee29bbad728f620cab5f47bd6e9c02f87d59f31a0dda/zope_interface-8.1.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efef80ddec4d7d99618ef71bc93b88859248075ca2e1ae1c78636654d3d55533", size = 264742, upload-time = "2025-11-15T08:37:05.613Z" },
{ url = "https://files.pythonhosted.org/packages/0b/0e/0cf77356862852d3d3e62db9aadae5419a1a7d89bf963b219745283ab5ca/zope_interface-8.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49aad83525eca3b4747ef51117d302e891f0042b06f32aa1c7023c62642f962b", size = 264252, upload-time = "2025-11-15T08:37:07.035Z" },
{ url = "https://files.pythonhosted.org/packages/8a/10/2af54aa88b2fa172d12364116cc40d325fedbb1877c3bb031b0da6052855/zope_interface-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:71cf329a21f98cb2bd9077340a589e316ac8a415cac900575a32544b3dffcb98", size = 212330, upload-time = "2025-11-15T08:37:08.14Z" },
]