added dramatiq and email when you log in
This commit is contained in:
parent
08d25b42a8
commit
e9e4597c96
11 changed files with 219 additions and 26 deletions
|
@ -5,3 +5,6 @@ DATABASE_HOST=postgres
|
|||
DATABASE_PORT=5432
|
||||
|
||||
DJANGO_ENV=
|
||||
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
|
|
|
@ -8,5 +8,4 @@
|
|||
2. `docker compose up`
|
||||
|
||||
## Adding new backend packages
|
||||
|
||||
`docker compose run --rm backend uv add <package>`
|
||||
|
|
|
@ -19,8 +19,7 @@ class LoginForm(AuthenticationForm):
|
|||
class RegisterForm(UserCreationForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = UserCreationForm.Meta.fields + \
|
||||
('first_name', 'last_name', 'email')
|
||||
fields = ('username', 'first_name', 'last_name', 'email')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -21,6 +21,11 @@ def auth_login(req: HttpRequest) -> HttpResponse:
|
|||
user = form.get_user()
|
||||
login(req, user)
|
||||
|
||||
user.email_user(
|
||||
subject="You have just logged in!",
|
||||
message="You have just logged in",
|
||||
)
|
||||
|
||||
redirect_url = getattr(req.GET, 'next', settings.LOGIN_REDIRECT_URL)
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
volumes:
|
||||
pg_data:
|
||||
|
||||
x-django: &x-django
|
||||
build:
|
||||
target: dev
|
||||
extends:
|
||||
file: docker-compose.build.yml
|
||||
service: backend
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- postgres
|
||||
environment:
|
||||
- DJANGO_ENV=${DJANGO_ENV}
|
||||
- DATABASE_HOST=postgres
|
||||
- DATABASE_URL=psql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
|
||||
- EMAIL_HOST_USER=${EMAIL_HOST_USER}
|
||||
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}
|
||||
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17
|
||||
|
@ -13,19 +31,18 @@ services:
|
|||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
|
||||
|
||||
backend:
|
||||
build:
|
||||
target: release
|
||||
extends:
|
||||
file: docker-compose.build.yml
|
||||
service: backend
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- postgres
|
||||
<<: *x-django
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DJANGO_ENV=${DJANGO_ENV}
|
||||
- DATABASE_HOST=postgres
|
||||
- DATABASE_URL=psql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
|
||||
|
||||
dramatiq:
|
||||
<<: *x-django
|
||||
command: ["/app/scripts/run-dramatiq.sh"]
|
||||
|
||||
gotenberg:
|
||||
image: gotenberg/gotenberg:8
|
||||
|
||||
redis:
|
||||
image: redis:7.4-alpine
|
||||
|
|
|
@ -19,35 +19,48 @@ env = Env()
|
|||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
INTERNAL_BASE_URL = "http://backend:8000"
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = env(
|
||||
'SECRET_KEY', default='django-insecure-+l+g3)q1zz2bz7=mz4ys6lhu5uj+=ucj34flm^clo4vb3(wmdp',
|
||||
)
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = env.bool('DEBUG', default=True)
|
||||
|
||||
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', str, default=['*'])
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
DJANGO_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'crispy_forms',
|
||||
'crispy_bootstrap5',
|
||||
]
|
||||
|
||||
MY_APPS = [
|
||||
'accounts.apps.AccountConfig',
|
||||
'subjects.apps.SubjectsConfig',
|
||||
'invoices.apps.InvoicesConfig',
|
||||
]
|
||||
|
||||
THIRD_PARTY_APPS = [
|
||||
'django_dramatiq',
|
||||
'crispy_forms',
|
||||
'crispy_bootstrap5',
|
||||
'post_office',
|
||||
]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
*DJANGO_APPS,
|
||||
*MY_APPS,
|
||||
*THIRD_PARTY_APPS,
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
|
@ -109,13 +122,10 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
|
||||
LANGUAGE_CODE = 'cs'
|
||||
LANGUAGES = [('en', _('English')), ('cs', _('Czech'))]
|
||||
|
||||
LOCALE_PATHS = [BASE_DIR / 'locale']
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
|
@ -140,3 +150,34 @@ AUTH_USER_MODEL = 'accounts.User'
|
|||
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
|
||||
|
||||
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
||||
|
||||
# Template to PDF rendering
|
||||
GOTENBERG_API_URL = env.url('GOTENBERG_API_URL', 'http://gotenberg:3000')
|
||||
|
||||
# Dramatiq
|
||||
DRAMATIQ_BROKER = {
|
||||
"BROKER": "dramatiq.brokers.redis.RedisBroker",
|
||||
"OPTIONS": {
|
||||
"url": "redis://redis:6379",
|
||||
},
|
||||
"MIDDLEWARE": [
|
||||
"dramatiq.middleware.AgeLimit",
|
||||
"dramatiq.middleware.TimeLimit",
|
||||
"dramatiq.middleware.Callbacks",
|
||||
"dramatiq.middleware.Retries",
|
||||
"django_dramatiq.middleware.DbConnectionsMiddleware",
|
||||
"django_dramatiq.middleware.AdminMiddleware",
|
||||
],
|
||||
}
|
||||
|
||||
# Email config
|
||||
EMAIL_BACKEND = 'post_office.EmailBackend'
|
||||
|
||||
EMAIL_HOST = env.str("EMAIL_HOST", "smtp.seznam.cz")
|
||||
EMAIL_PORT = env.int("EMAIL_PORT", 465)
|
||||
EMAIL_HOST_USER = env.str("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
|
||||
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", True)
|
||||
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False)
|
||||
|
||||
DEFAULT_FROM_EMAIL = env.str("EMAIL_DEFAULT_FROM", "Facturio <no-reply@kropcloud.net>")
|
||||
|
|
6
invoices/tasks.py
Normal file
6
invoices/tasks.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import dramatiq
|
||||
|
||||
@dramatiq.actor()
|
||||
def generate_pdf(url: str):
|
||||
print(url)
|
||||
pass
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest
|
||||
|
@ -8,6 +9,7 @@ from django.urls import reverse
|
|||
|
||||
from . import forms
|
||||
from . import models
|
||||
from . import tasks
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -24,6 +26,9 @@ def home(req: HttpRequest) -> HttpResponse:
|
|||
)
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
tasks.generate_pdf.send(
|
||||
settings.INTERNAL_BASE_URL + reverse('invoices:print_invoice', kwargs=dict(invoice_id=invoice.id)),
|
||||
)
|
||||
return redirect(reverse('invoices:invoice', kwargs=dict(invoice_id=invoice.id)))
|
||||
else:
|
||||
transaction.set_rollback(True)
|
||||
|
@ -46,7 +51,7 @@ def view_invoice(req: HttpRequest, invoice_id: int) -> HttpResponse:
|
|||
return render(req, 'invoices/view.html', dict(invoice=invoice))
|
||||
|
||||
|
||||
@login_required
|
||||
# @login_required
|
||||
def print_invoice(req: HttpRequest, invoice_id: int):
|
||||
invoice = models.Invoice.objects.get(pk=invoice_id)
|
||||
return render(req, 'invoices/invoice.html', dict(invoice=invoice))
|
||||
|
|
|
@ -9,7 +9,10 @@ dependencies = [
|
|||
"crispy-bootstrap5>=2024.10",
|
||||
"django>=5.1.6",
|
||||
"django-crispy-forms>=2.3",
|
||||
"django-dramatiq>=0.13.0",
|
||||
"django-environ>=0.12.0",
|
||||
"django-post-office>=3.9.1",
|
||||
"dramatiq[redis]>=1.17.1",
|
||||
"psycopg[c]>=3.2.5",
|
||||
"uvicorn[standard]>=0.34.0",
|
||||
"wait-for-it>=2.3.0",
|
||||
|
|
10
scripts/run-dramatiq.sh
Executable file
10
scripts/run-dramatiq.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o nounset
|
||||
set -o xtrace
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
|
||||
echo "Running dramatiq..."
|
||||
./manage.py rundramatiq -p 2 -t 2
|
105
uv.lock
105
uv.lock
|
@ -37,6 +37,23 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bleach"
|
||||
version = "6.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "webencodings" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
css = [
|
||||
{ name = "tinycss2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
|
@ -159,6 +176,19 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/4c/3b/5dc3faf8739d1ce7a73cedaff508b4af8f6aa1684120ded6185ca0c92734/django_crispy_forms-2.3-py3-none-any.whl", hash = "sha256:efc4c31e5202bbec6af70d383a35e12fc80ea769d464fb0e7fe21768bb138a20", size = 31411 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-dramatiq"
|
||||
version = "0.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "dramatiq" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/89/bb40b3287acea4e7451ee66a82a75bdeb3814252759d2e39c08c4a02a5f8/django_dramatiq-0.13.0.tar.gz", hash = "sha256:1cdf723ec6a223761985c0855b92fe40cca85d364914df6d99d3034f0f6cee15", size = 15535 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/09/b19725f405621e41c119c6f4b43359c281ddd471794c215b754b8903f52c/django_dramatiq-0.13.0-py3-none-any.whl", hash = "sha256:855c590606c74bd478ad0a3a588667e3e73419e47e43ef2ae047dbebe19dff0b", size = 12376 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-environ"
|
||||
version = "0.12.0"
|
||||
|
@ -168,6 +198,36 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-post-office"
|
||||
version = "3.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "bleach", extra = ["css"] },
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/e9/7369fb453269ded8d2fff58299fe653033702bd15a427bf35353d4606df3/django-post_office-3.9.1.tar.gz", hash = "sha256:3d8f502f7829e4cf83e830d9efd6909bb44690af6bc41c7e4fc5a85d7b04df10", size = 76624 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/12/19fdf17fb1a0d5e4418798cf9ed66b8aee39da564e875ec025627e08abdf/django_post_office-3.9.1-py3-none-any.whl", hash = "sha256:0082c4dd17854f66077ef457cf868c9bc5b6247de99d5755c45ddea1c518ed6f", size = 85671 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dramatiq"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "prometheus-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/7a/6792ddc64a77d22bfd97261b751a7a76cf2f9d62edc59aafb679ac48b77d/dramatiq-1.17.1.tar.gz", hash = "sha256:2675d2f57e0d82db3a7d2a60f1f9c536365349db78c7f8d80a63e4c54697647a", size = 99071 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/36/925c7afd5db4f1a3f00676b9c3c58f31ff7ae29a347282d86c8d429280a5/dramatiq-1.17.1-py3-none-any.whl", hash = "sha256:951cdc334478dff8e5150bb02a6f7a947d215ee24b5aedaf738eff20e17913df", size = 120382 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
redis = [
|
||||
{ name = "redis" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "facturio"
|
||||
version = "0.1.0"
|
||||
|
@ -177,7 +237,10 @@ dependencies = [
|
|||
{ name = "crispy-bootstrap5" },
|
||||
{ name = "django" },
|
||||
{ name = "django-crispy-forms" },
|
||||
{ name = "django-dramatiq" },
|
||||
{ name = "django-environ" },
|
||||
{ name = "django-post-office" },
|
||||
{ name = "dramatiq", extra = ["redis"] },
|
||||
{ name = "psycopg", extra = ["c"] },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
{ name = "wait-for-it" },
|
||||
|
@ -194,7 +257,10 @@ requires-dist = [
|
|||
{ name = "crispy-bootstrap5", specifier = ">=2024.10" },
|
||||
{ name = "django", specifier = ">=5.1.6" },
|
||||
{ name = "django-crispy-forms", specifier = ">=2.3" },
|
||||
{ name = "django-dramatiq", specifier = ">=0.13.0" },
|
||||
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||
{ name = "django-post-office", specifier = ">=3.9.1" },
|
||||
{ name = "dramatiq", extras = ["redis"], specifier = ">=1.17.1" },
|
||||
{ name = "psycopg", extras = ["c"], specifier = ">=3.2.5" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
|
||||
{ name = "wait-for-it", specifier = ">=2.3.0" },
|
||||
|
@ -295,6 +361,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.21.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.5"
|
||||
|
@ -354,6 +429,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
|
@ -387,6 +471,18 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "webencodings" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
|
@ -520,6 +616,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0"
|
||||
|
|
Reference in a new issue