1 Commits
v1.0 ... v0.2

Author SHA1 Message Date
eneller
c5ef95d69b fix: table headers 2025-03-20 21:53:26 +01:00
25 changed files with 136 additions and 263 deletions

View File

@@ -3,6 +3,4 @@ A simple Website to provide a `NNI (Non-Nerd Interface)` to [epub2go.py](https:/
## Development ## Development
This project uses [watchman](https://facebook.github.io/watchman/) for file watching and reloading. This project uses [watchman](https://facebook.github.io/watchman/) for file watching and reloading.
Follow the [official instructions](https://facebook.github.io/watchman/docs/install.html) for your system to install, django will default to its standard watcher otherwise. Follow the [official instructions](https://facebook.github.io/watchman/docs/install.html) for your system to install, django will default to its standard watcher otherwise.
Also, [celery](https://docs.celeryq.dev/en/stable/) is used as a task queue with [redis](https://hub.docker.com/_/redis) as backend.

View File

@@ -1,6 +0,0 @@
name: task-queue
services:
redis:
ports:
- 6379:6379
image: redis

View File

@@ -1,15 +0,0 @@
server {
listen 80;
server_name epub2go.example.org;
access_log /var/log/nginx/epub2go.log;
location /static {
alias /www/epub2go;
}
location / {
proxy_pass http://127.0.0.1:50000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@@ -5,13 +5,11 @@ description = "Web Interface to epub2go.py"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"celery[redis]>=5.4.0", "celery>=5.4.0",
"django>=5.1.6", "django>=5.1.6",
"epub2go", "epub2go",
"gunicorn>=23.0.0",
"python-dotenv>=1.0.1", "python-dotenv>=1.0.1",
"pywatchman>=2.0.0", "pywatchman>=2.0.0",
"redis>=5.2.1",
] ]
[tool.uv.sources] [tool.uv.sources]

View File

@@ -1,23 +1,24 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpRequest, HttpResponse, FileResponse, HttpResponseBadRequest from django.http import HttpRequest, HttpResponse, FileResponse, HttpResponseBadRequest
from django.conf import settings
from celery import shared_task
from epub2go.convert import get_all_books, Book, allbooks_url from epub2go.convert import get_all_books, Book, GBConvert, allbooks_url
import os import os
from urllib.parse import urlparse from urllib.parse import urlparse
import logging import logging
from epub2go_web.tasks import getEpub
logger = logging.getLogger(__name__) #TODO configure logging logger = logging.getLogger(__name__) #TODO configure logging
converter = GBConvert(downloaddir=settings.MEDIA_ROOT)
books = sorted(get_all_books(), key= lambda b: b.title)# TODO get from pickle books = sorted(get_all_books(), key= lambda b: b.title)# TODO get from pickle
gbnetloc = urlparse(allbooks_url).netloc gbnetloc = urlparse(allbooks_url).netloc
def index(request: HttpRequest): def index(request: HttpRequest):
context = { context = {
'title': 'epub2go', 'title': 'epub2go',
'http_host': f'http://{ request.META['HTTP_HOST'] }', 'http_host': request.META['HTTP_HOST'],
'books': books, 'books': books,
'book_count': len(books), 'book_count': len(books),
'allbooks_url': allbooks_url, 'allbooks_url': allbooks_url,
@@ -27,8 +28,7 @@ def index(request: HttpRequest):
if targetParam: if targetParam:
if validateUrl(targetParam): if validateUrl(targetParam):
# download file # download file
result = getEpub.delay(targetParam) fpath = getEpub(targetParam)
fpath = result.get(timeout=60)
fname = os.path.basename(fpath) fname = os.path.basename(fpath)
file = open(fpath, 'rb') file = open(fpath, 'rb')
response = FileResponse(file) response = FileResponse(file)
@@ -38,7 +38,7 @@ def index(request: HttpRequest):
else: return HttpResponseBadRequest('Input URL invalid.') else: return HttpResponseBadRequest('Input URL invalid.')
else: else:
# return base view # return base view
return render(request, 'home.html', context) return render(request, 'index.html', context)
def validateUrl(param)->bool : def validateUrl(param)->bool :
@@ -46,3 +46,9 @@ def validateUrl(param)->bool :
if(netloc == gbnetloc): return True if(netloc == gbnetloc): return True
return False return False
# TODO make this async and show some indication of progress/loading
#@shared_task
def getEpub(book_url):
# TODO check for existing file and age
return converter.download(book_url)

View File

@@ -1,5 +0,0 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

View File

@@ -1,23 +0,0 @@
# Start Celery workers from src/ using `celery -A epub2go_web worker --loglevel=INFO`
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'epub2go_web.settings')
app = Celery('epub2go_web')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@app.task(bind=True, ignore_result=True)
def debug_task(self):
print(f'Request: {self.request!r}')

View File

@@ -32,6 +32,8 @@ ALLOWED_HOSTS = ['*']
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
@@ -44,6 +46,7 @@ MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
@@ -61,6 +64,7 @@ TEMPLATES = [
"context_processors": [ "context_processors": [
"django.template.context_processors.debug", "django.template.context_processors.debug",
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
], ],
}, },
@@ -69,18 +73,48 @@ TEMPLATES = [
WSGI_APPLICATION = "epub2go_web.wsgi.application" WSGI_APPLICATION = "epub2go_web.wsgi.application"
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Celery settings # Database
CELERY_BROKER_URL = "redis://localhost:6379/0" # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
CELERY_TASK_ALWAYS_EAGER = True #DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
# }
#}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/ # https://docs.djangoproject.com/en/5.1/howto/static-files/
@@ -93,3 +127,8 @@ STATIC_ROOT = PROJ_DIR/ "staticfiles"
MEDIA_URL = "media/" MEDIA_URL = "media/"
MEDIA_ROOT = PROJ_DIR / "media/" MEDIA_ROOT = PROJ_DIR / "media/"
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -7,7 +7,6 @@ const table_r = Array.from(table.getElementsByClassName('table-entry'));
document.addEventListener('keydown', (event)=>{ document.addEventListener('keydown', (event)=>{
if (event.ctrlKey && event.key === 'k'){ if (event.ctrlKey && event.key === 'k'){
event.preventDefault(); event.preventDefault();
searchInput.focus();
searchInput.select(); searchInput.select();
} }
}); });

View File

@@ -1,21 +0,0 @@
{
"name": "epub2go Converter",
"short_name": "epub2go",
"icons": [
{
"src": "/static/favicon/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/favicon/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -11,13 +11,12 @@ html{
} }
body{ body{
background-color: var(--bg); background-color: var(--bg);
overflow-x: hidden; margin:40px auto;
margin:.5em auto; max-width:800px;
max-width:min(100%,800px);
line-height:1.4; line-height:1.4;
font-size:18px; font-size:18px;
color:var(--fg); color:var(--fg);
padding:0 .2em; padding:0 10px;
} }
h1,h2,h3{ h1,h2,h3{
@@ -27,18 +26,14 @@ h1,h2,h3{
header{ header{
text-align: center; text-align: center;
justify-content: center; justify-content: center;
margin-bottom: 4%;
} }
small{ small{
color: var(--fg-deemph); color: var(--fg-deemph);
} }
.search-bar{ .searchbar{
width: 100%; width: 100%;
} }
.search-container{
text-align: center;
justify-content: center;
margin-bottom: 4%;
}
#searchInput { #searchInput {
background-color: var(--bg); background-color: var(--bg);
width: 100%; width: 100%;
@@ -48,13 +43,9 @@ small{
border-color: var(--bg-acc); border-color: var(--bg-acc);
box-shadow: var(--bg-acc) 2px 2px; box-shadow: var(--bg-acc) 2px 2px;
} }
.main-banner{
text-align: center;
}
table, tr{ table, tr{
/* make table not resize when elements are hidden by searching */ /* make table not resize when elements are hidden by searching */
width: 100%; width: 100%;
word-break: break-all;
} }
th{ th{
text-align: left; text-align: left;
@@ -79,9 +70,9 @@ a:hover, a:any-link{
color: inherit; color: inherit;
} }
.table-link{ .table-link{
/* TODO fix links with no title/content being almost unclickable */
display: block; display: block;
width: 100%; width: 100%;
min-height: 1em;
height: 100%; height: 100%;
padding: 1px; padding: 1px;
} }

View File

@@ -1,11 +0,0 @@
from celery import shared_task
from django.conf import settings
from epub2go.convert import GBConvert
converter = GBConvert(downloaddir=settings.MEDIA_ROOT)
@shared_task
def getEpub(book_url):
# TODO check for existing file and age
return converter.download(book_url)

View File

@@ -1,49 +0,0 @@
{% extends 'index.html' %}
{% load static %}
{% block main %}
<main>
<div class="search-container">
<search>
<form onsubmit="submitSearch(event)" class="search-bar">
<input type="search" id="searchInput" placeholder="Suche nach Titel" minlength="3">
</form>
</search>
<small>Im Moment finden sich hier <a href="{{ allbooks_url }}">{{ book_count }} Bücher.</a> </small>
<a href="javascript:void(window.open('{{ http_host }}/?t=' + encodeURIComponent(window.location.toString())))" title="Als Lesezeichen speichern">
<img src="{% static 'bookmark.svg' %}" alt="Bookmarklet" class="header-icon">
</a>
<a href="https://github.com/eneller/epub2go-web" title="Projekt auf GitHub ansehen">
<img src="{% static 'github.svg' %}" alt="GitHub" class="header-icon">
</a>
</div>
<!-- NOTE use dl here?-->
<table id="table">
<thead>
<tr>
<th></th>
<th>Titel</th>
<th>Autor</th>
</tr>
</thead>
<tbody>
{% for item in books %}
<tr class="table-entry">
<td>
<a href= {{ item.url }} target="_blank" rel="noopener noreferrer" class="table-link">
<img src="{% static 'open-link.svg' %}" alt="Open Link" class="inline-icon">
</a>
</td>
<td class="table-data">
<a href="./?t= {{ item.url }}" class="table-link">
{{ item.title }}
</a>
</td>
<td>
<a> {{ item.author }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
{% endblock %}

View File

@@ -6,22 +6,57 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0" > <meta name="viewport" content="width=device-width,initial-scale=1.0" >
<link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}">
<link rel="icon" type="image/png" href="{% static '/favicon/favicon-96x96.png' %}" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="{% static '/favicon/favicon.svg' %}" />
<link rel="shortcut icon" href="{% static '/favicon/favicon.ico' %}" />
<link rel="apple-touch-icon" sizes="180x180" href="{% static '/favicon/apple-touch-icon.png' %}" />
<meta name="apple-mobile-web-app-title" content="epub2go" />
<link rel="manifest" href="{% static 'site.webmanifest' %}" />
</head> </head>
<body> <body>
<header> <header>
<h1><a href="{{ http_host }}">{{ title }}</a></h1> <div>
<h1>{{ title }}</h1>
<h2>1 Click Download für Literatur</h2> <h2>1 Click Download für Literatur</h2>
</div>
<search>
<form onsubmit="submitSearch(event)" class="searchbar">
<input type="search" id="searchInput" placeholder="Suche nach Titel" minlength="3">
</form>
</search>
<small>Im Moment finden sich hier <a href="{{ allbooks_url }}">{{ book_count }} Bücher.</a> </small>
<a href="javascript:void(window.open('http://{{ http_host }}/?t=' + encodeURIComponent(window.location.toString())))" title="Als Lesezeichen speichern"><!--TODO fix domain part as variable-->
<img src="{% static 'bookmark.svg' %}" alt="Bookmarklet" class="header-icon">
</a>
<a href="https://github.com/eneller/epub2go-web">
<img src="{% static 'github.svg' %}" alt="GitHub" class="header-icon">
</a>
</header> </header>
{% block main %} <main>
{% endblock %} <!-- NOTE use dl here?-->
<table id="table">
<thead>
<tr>
<th></th>
<th>Titel</th>
<th>Autor</th>
</tr>
</thead>
<tbody>
{% for item in books %}
<tr class="table-entry">
<td>
<a href= {{ item.url }} target="_blank" rel="noopener noreferrer" class="table-link">
<img src="{% static 'open-link.svg' %}" alt="Open Link" class="inline-icon">
</a>
</td>
<td class="table-data">
<a href="./?t= {{ item.url }}" class="table-link">
{{ item.title }}
</a>
</td>
<td>
<a> {{ item.author }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
<footer> <footer>
</footer> </footer>
</body> </body>

View File

@@ -1,6 +0,0 @@
{% extends 'index.html' %}
{% block main %}
<main>
<p class="main-banner">Einen Moment bitte. Deine Datei wird heruntergeladen.</p>
</main>
{% endblock %}

View File

@@ -15,6 +15,7 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
@@ -22,5 +23,6 @@ from epub2go.convert import get_all_books, GBConvert, Book
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls),
path('',include('core.urls')) path('',include('core.urls'))
] ]

View File

@@ -1,18 +0,0 @@
# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "epub2go_web.wsgi"
# The granularity of Error log outputs
loglevel = "debug"
# The number of worker processes for handling requests
workers = 4
# The socket to bind
bind = "127.0.0.1:50000"
# Restart workers when code changes (development only!)
#reload = True
# Write access and error info to /var/log
#accesslog = errorlog = "/var/log/gunicorn/dev.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
#pidfile = "/var/run/gunicorn/dev.pid"
# Daemonize the Gunicorn process (detach & enter background)
#daemon = True

72
uv.lock generated
View File

@@ -46,7 +46,7 @@ wheels = [
[[package]] [[package]]
name = "celery" name = "celery"
version = "5.5.0" version = "5.4.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "billiard" }, { name = "billiard" },
@@ -56,16 +56,12 @@ dependencies = [
{ name = "click-repl" }, { name = "click-repl" },
{ name = "kombu" }, { name = "kombu" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "tzdata" },
{ name = "vine" }, { name = "vine" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/07/0c/0aaf310f4a2a048f010ebf63b8b0aaeb02ede2a79864fdbcdf31757a139d/celery-5.5.0.tar.gz", hash = "sha256:10d49f9926d16237310109b0e6e1e2f7a2133b84e684bb36534d7663e66919bb", size = 1665645 } sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/98/d4eefe492ef810f304203cfff0f60554fab95514ec51b701a4529b66fa92/celery-5.5.0-py3-none-any.whl", hash = "sha256:f4170c6e5952281318448a899d9e9a15b9cbd007e002091766900dc8f71b9394", size = 438396 }, { url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983 },
]
[package.optional-dependencies]
redis = [
{ name = "redis" },
] ]
[[package]] [[package]]
@@ -186,8 +182,8 @@ wheels = [
[[package]] [[package]]
name = "epub2go" name = "epub2go"
version = "2.2.1" version = "2.1"
source = { git = "https://github.com/eneller/epub2go.py#401d02e0ca26ca0a12ee331140a2cde273559251" } source = { git = "https://github.com/eneller/epub2go.py#42677007633c1a4f7f87a2f8f02b9faa1715f037" }
dependencies = [ dependencies = [
{ name = "beautifulsoup4" }, { name = "beautifulsoup4" },
{ name = "click" }, { name = "click" },
@@ -202,36 +198,20 @@ name = "epub2go-py-web"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "celery", extra = ["redis"] }, { name = "celery" },
{ name = "django" }, { name = "django" },
{ name = "epub2go" }, { name = "epub2go" },
{ name = "gunicorn" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "pywatchman" }, { name = "pywatchman" },
{ name = "redis" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "celery", extras = ["redis"], specifier = ">=5.4.0" }, { name = "celery", specifier = ">=5.4.0" },
{ name = "django", specifier = ">=5.1.6" }, { name = "django", specifier = ">=5.1.6" },
{ name = "epub2go", git = "https://github.com/eneller/epub2go.py" }, { name = "epub2go", git = "https://github.com/eneller/epub2go.py" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "python-dotenv", specifier = ">=1.0.1" }, { name = "python-dotenv", specifier = ">=1.0.1" },
{ name = "pywatchman", specifier = ">=2.0.0" }, { name = "pywatchman", specifier = ">=2.0.0" },
{ name = "redis", specifier = ">=5.2.1" },
]
[[package]]
name = "gunicorn"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 },
] ]
[[package]] [[package]]
@@ -245,25 +225,16 @@ wheels = [
[[package]] [[package]]
name = "kombu" name = "kombu"
version = "5.5.2" version = "5.5.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "amqp" }, { name = "amqp" },
{ name = "tzdata" }, { name = "tzdata" },
{ name = "vine" }, { name = "vine" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/c8/12/7a340f48920f30d6febb65d0c4aca70ed01b29e116131152977df78a9a39/kombu-5.5.2.tar.gz", hash = "sha256:2dd27ec84fd843a4e0a7187424313f87514b344812cb98c25daddafbb6a7ff0e", size = 461522 } sdist = { url = "https://files.pythonhosted.org/packages/0f/a5/eddeaf1044b94d802ba9d69f5446f758a14b6770bcfd7498d5270fc2e68f/kombu-5.5.0.tar.gz", hash = "sha256:72e65c062e903ee1b4e8b68d348f63c02afc172eda409e3aca85867752e79c0b", size = 460768 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/af/ba/939f3db0fca87715c883e42cc93045347d61a9d519c270a38e54a06db6e1/kombu-5.5.2-py3-none-any.whl", hash = "sha256:40f3674ed19603b8a771b6c74de126dbf8879755a0337caac6602faa82d539cd", size = 209763 }, { url = "https://files.pythonhosted.org/packages/23/29/f4f109c490d3cab3cbbba53403895e6052c84a40f18c28f75cd4dd53f11d/kombu-5.5.0-py3-none-any.whl", hash = "sha256:526c6cf038c986b998639109a1eb762502f831e8da148cc928f1f95cd91eb874", size = 209313 },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
] ]
[[package]] [[package]]
@@ -301,11 +272,11 @@ wheels = [
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.1.0" version = "1.0.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
] ]
[[package]] [[package]]
@@ -317,15 +288,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/7f/5d68d803489770cffa5d2b44be99b978c866f8a4d8e835f9da850415ed8a/pywatchman-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:51c2b4c72bea6b9fd90caf20759f5bc47febf0fd27bf2f247b87c66e2f6bab02", size = 52557 }, { url = "https://files.pythonhosted.org/packages/51/7f/5d68d803489770cffa5d2b44be99b978c866f8a4d8e835f9da850415ed8a/pywatchman-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:51c2b4c72bea6b9fd90caf20759f5bc47febf0fd27bf2f247b87c66e2f6bab02", size = 52557 },
] ]
[[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]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@@ -382,11 +344,11 @@ wheels = [
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2025.2" version = "2025.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
] ]
[[package]] [[package]]