made things prettier
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
import logging
|
||||
import markdown
|
||||
from markupsafe import Markup
|
||||
from logging.handlers import WatchedFileHandler
|
||||
from flask import Flask
|
||||
from flask_socketio import SocketIO
|
||||
@@ -49,4 +51,10 @@ def create_app(config_class=Config):
|
||||
app.register_blueprint(slides_bp)
|
||||
app.register_blueprint(admin_bp)
|
||||
|
||||
@app.template_filter('md')
|
||||
def render_markdown(text):
|
||||
if not text:
|
||||
return ''
|
||||
return Markup(markdown.markdown(text, extensions=['nl2br']))
|
||||
|
||||
return app
|
||||
|
||||
744
app/package-lock.json
generated
744
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"flowbite": "^3.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
|
||||
@@ -8,82 +8,178 @@ bidict==0.23.1
|
||||
# via python-socketio
|
||||
blinker==1.9.0
|
||||
# via flask
|
||||
brotli==1.2.0
|
||||
# via geventhttpclient
|
||||
cachetools==6.2.6 ; python_full_version < '3.10'
|
||||
# via tutor-tool
|
||||
cachetools==7.0.1 ; python_full_version >= '3.10'
|
||||
# via tutor-tool
|
||||
certifi==2026.1.4
|
||||
# via
|
||||
# geventhttpclient
|
||||
# requests
|
||||
cffi==2.0.0 ; (implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (implementation_name == 'pypy' and sys_platform != 'win32') or (platform_python_implementation == 'CPython' and sys_platform == 'win32')
|
||||
# via
|
||||
# gevent
|
||||
# pyzmq
|
||||
charset-normalizer==3.4.4
|
||||
# via requests
|
||||
click==8.1.8 ; python_full_version < '3.10'
|
||||
# via flask
|
||||
click==8.2.1 ; python_full_version >= '3.10'
|
||||
# via flask
|
||||
colorama==0.4.6 ; sys_platform == 'win32'
|
||||
# via click
|
||||
# via
|
||||
# click
|
||||
# pytest
|
||||
configargparse==1.7.1
|
||||
# via locust
|
||||
dnspython==2.7.0 ; python_full_version < '3.10'
|
||||
# via eventlet
|
||||
dnspython==2.8.0 ; python_full_version >= '3.10'
|
||||
# via eventlet
|
||||
eventlet==0.40.3
|
||||
# via tutor-tool
|
||||
exceptiongroup==1.3.1 ; python_full_version == '3.10.*'
|
||||
# via pytest
|
||||
flask==3.1.2
|
||||
# via
|
||||
# flask-bcrypt
|
||||
# flask-cors
|
||||
# flask-login
|
||||
# flask-socketio
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
# locust
|
||||
# tutor-tool
|
||||
flask-bcrypt==1.0.1
|
||||
# via tutor-tool
|
||||
flask-cors==6.0.2
|
||||
# via locust
|
||||
flask-login==0.6.3
|
||||
# via tutor-tool
|
||||
# via
|
||||
# locust
|
||||
# tutor-tool
|
||||
flask-socketio==5.5.1
|
||||
# via tutor-tool
|
||||
flask-sqlalchemy==3.1.1
|
||||
# via tutor-tool
|
||||
flask-wtf==1.2.2
|
||||
# via tutor-tool
|
||||
gevent==25.9.1
|
||||
# via
|
||||
# geventhttpclient
|
||||
# locust
|
||||
geventhttpclient==2.3.7
|
||||
# via locust
|
||||
greenlet==3.2.4
|
||||
# via
|
||||
# eventlet
|
||||
# gevent
|
||||
# sqlalchemy
|
||||
gunicorn==23.0.0
|
||||
# via tutor-tool
|
||||
h11==0.16.0
|
||||
# via wsproto
|
||||
idna==3.11
|
||||
# via requests
|
||||
importlib-metadata==8.7.0 ; python_full_version < '3.10'
|
||||
# via flask
|
||||
# via
|
||||
# flask
|
||||
# markdown
|
||||
iniconfig==2.3.0 ; python_full_version >= '3.10'
|
||||
# via pytest
|
||||
itsdangerous==2.2.0
|
||||
# via
|
||||
# flask
|
||||
# flask-wtf
|
||||
jinja2==3.1.6
|
||||
# via flask
|
||||
locust==2.34.0 ; python_full_version < '3.10'
|
||||
# via tutor-tool
|
||||
locust==2.43.3 ; python_full_version >= '3.10'
|
||||
# via tutor-tool
|
||||
markdown==3.9 ; python_full_version < '3.10'
|
||||
# via tutor-tool
|
||||
markdown==3.10.2 ; python_full_version >= '3.10'
|
||||
# via tutor-tool
|
||||
markupsafe==3.0.2
|
||||
# via
|
||||
# flask
|
||||
# jinja2
|
||||
# werkzeug
|
||||
# wtforms
|
||||
msgpack==1.1.2
|
||||
# via locust
|
||||
packaging==25.0
|
||||
# via gunicorn
|
||||
# via
|
||||
# gunicorn
|
||||
# pytest
|
||||
pluggy==1.6.0 ; python_full_version >= '3.10'
|
||||
# via pytest
|
||||
psutil==7.2.2
|
||||
# via locust
|
||||
psycopg2-binary==2.9.11
|
||||
# via tutor-tool
|
||||
pycparser==2.23 ; (python_full_version < '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (python_full_version < '3.10' and implementation_name != 'PyPy' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (python_full_version < '3.10' and implementation_name == 'pypy' and sys_platform != 'win32')
|
||||
# via cffi
|
||||
pycparser==3.0 ; (python_full_version >= '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (python_full_version >= '3.10' and implementation_name != 'PyPy' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (python_full_version >= '3.10' and implementation_name == 'pypy' and sys_platform != 'win32')
|
||||
# via cffi
|
||||
pygments==2.19.2 ; python_full_version >= '3.10'
|
||||
# via pytest
|
||||
pytest==9.0.2 ; python_full_version >= '3.10'
|
||||
# via locust
|
||||
python-engineio==4.12.2
|
||||
# via python-socketio
|
||||
# via
|
||||
# locust
|
||||
# python-socketio
|
||||
python-socketio==5.13.0
|
||||
# via flask-socketio
|
||||
# via
|
||||
# flask-socketio
|
||||
# locust
|
||||
pytz==2025.2
|
||||
# via tutor-tool
|
||||
pywin32==311 ; sys_platform == 'win32'
|
||||
# via locust
|
||||
pyzmq==27.1.0
|
||||
# via locust
|
||||
redis==7.0.1 ; python_full_version < '3.10'
|
||||
# via tutor-tool
|
||||
redis==7.2.0 ; python_full_version >= '3.10'
|
||||
# via tutor-tool
|
||||
requests==2.32.5
|
||||
# via
|
||||
# locust
|
||||
# python-socketio
|
||||
setuptools==82.0.0 ; python_full_version < '3.10'
|
||||
# via
|
||||
# locust
|
||||
# zope-event
|
||||
simple-websocket==1.1.0
|
||||
# via python-engineio
|
||||
sqlalchemy==2.0.43
|
||||
# via flask-sqlalchemy
|
||||
tomli==2.4.0 ; python_full_version < '3.11'
|
||||
# via
|
||||
# locust
|
||||
# pytest
|
||||
typing-extensions==4.15.0
|
||||
# via sqlalchemy
|
||||
# via
|
||||
# exceptiongroup
|
||||
# locust
|
||||
# sqlalchemy
|
||||
urllib3==2.6.3
|
||||
# via
|
||||
# geventhttpclient
|
||||
# requests
|
||||
websocket-client==1.9.0 ; python_full_version >= '3.10'
|
||||
# via python-socketio
|
||||
werkzeug==3.1.3
|
||||
# via
|
||||
# flask
|
||||
# flask-cors
|
||||
# flask-login
|
||||
# locust
|
||||
wsproto==1.2.0
|
||||
# via simple-websocket
|
||||
wtforms==3.2.1
|
||||
@@ -92,3 +188,11 @@ xkcdpass==1.20.0
|
||||
# via tutor-tool
|
||||
zipp==3.23.0 ; python_full_version < '3.10'
|
||||
# via importlib-metadata
|
||||
zope-event==6.0 ; python_full_version < '3.10'
|
||||
# via gevent
|
||||
zope-event==6.1 ; python_full_version >= '3.10'
|
||||
# via gevent
|
||||
zope-interface==8.0.1 ; python_full_version < '3.10'
|
||||
# via gevent
|
||||
zope-interface==8.2 ; python_full_version >= '3.10'
|
||||
# via gevent
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('flowbite/plugin')
|
||||
require('flowbite/plugin'),
|
||||
require('@tailwindcss/typography')
|
||||
],
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
{% block body %}
|
||||
|
||||
{# ── Header ────────────────────────────────────────────────────── #}
|
||||
<header class="bg-surface border-b border-border shadow-sm">
|
||||
<div class="w-full px-6 py-3 flex items-center justify-between gap-4">
|
||||
<header class="w-full px-4 pt-4 pb-2 md:px-6 md:py-3">
|
||||
|
||||
{# Left: user + logout #}
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium truncate">
|
||||
{{ user_name }}
|
||||
</span>
|
||||
{# ── Desktop: single row ───────────────────────────── #}
|
||||
<div class="hidden md:grid md:grid-cols-3 items-center gap-4">
|
||||
|
||||
{# Col 1: user + logout (left-aligned) #}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium">{{ user_name }}</span>
|
||||
<form method="POST" action="/logout">
|
||||
<button type="submit"
|
||||
class="text-xs text-white bg-red-600 px-2.5 py-1 rounded-md hover:bg-red-700 transition">
|
||||
@@ -27,12 +27,40 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# Center: course switcher #}
|
||||
{# Col 2: course switcher (perfectly centered) #}
|
||||
<div class="flex justify-center">
|
||||
{{ course_switcher(courses, active_course_name, '/switch_course') }}
|
||||
</div>
|
||||
|
||||
{# Right: social links + admin #}
|
||||
{# Col 3: social + admin (right-aligned) #}
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
<a href="https://zulip.in.tum.de/#narrow/channel/3234-ITSec-Tutorium-Simon-Bu.C3.9Fmann" target="_blank">
|
||||
<img src="{{ url_for('static', filename='zulip_icon.svg') }}" class="h-7 w-7">
|
||||
</a>
|
||||
<a href="https://discordapp.com/users/306086176585154560" target="_blank">
|
||||
<img src="{{ url_for('static', filename='discord_icon.svg') }}" class="h-7 w-7">
|
||||
</a>
|
||||
<a href="{{ url_for('admin.admin') }}">
|
||||
<button class="bg-primary hover:bg-blue-700 text-white text-sm px-3 py-1 rounded-md transition">
|
||||
Admin
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{# ── Mobile: single row only ──────────────────────── #}
|
||||
<div class="flex flex-col gap-3 md:hidden">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium">{{ user_name }}</span>
|
||||
<form method="POST" action="/logout">
|
||||
<button type="submit"
|
||||
class="text-xs text-white bg-red-600 px-2.5 py-1 rounded-md hover:bg-red-700 transition">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="https://zulip.in.tum.de/#narrow/channel/3234-ITSec-Tutorium-Simon-Bu.C3.9Fmann" target="_blank">
|
||||
<img src="{{ url_for('static', filename='zulip_icon.svg') }}" class="h-7 w-7">
|
||||
@@ -47,6 +75,8 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
{# ── Main Content ──────────────────────────────────────────────── #}
|
||||
@@ -60,6 +90,9 @@
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex justify-center md:hidden">
|
||||
{{ course_switcher(courses, active_course_name, '/switch_course') }}
|
||||
</div>
|
||||
|
||||
{# ── Password Form ─────────────────────────────────────────── #}
|
||||
<section class="flex justify-center">
|
||||
@@ -110,7 +143,9 @@
|
||||
<a href="/slides/{{ deck.course.folder }}/{{ deck.deck }}/{{ deck.index_file }}"
|
||||
class="flex-grow block hover:text-primary transition">
|
||||
<h3 class="text-lg font-semibold mb-1">{{ deck.name }}</h3>
|
||||
<p class="text-muted text-sm">{{ deck.description }}</p>
|
||||
<div class="prose prose-sm dark:prose-invert prose-p:text-muted prose-li:text-muted">
|
||||
{{ deck.description | md }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{% if deck.export_file %}
|
||||
|
||||
@@ -81,8 +81,11 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick="toggleCourseSwitcher()"
|
||||
class="flex items-center gap-2 px-4 py-1.5 rounded-lg border border-border bg-surface
|
||||
text-sm font-medium text-text hover:border-primary hover:text-primary transition whitespace-nowrap">
|
||||
class="flex items-center gap-2 px-4 py-1.5 rounded-full
|
||||
bg-surface shadow-md ring-1 ring-border
|
||||
text-sm font-medium text-text
|
||||
hover:ring-primary hover:text-primary
|
||||
transition whitespace-nowrap">
|
||||
<span id="course-switcher-label">{{ active_course_name }}</span>
|
||||
<svg class="w-3.5 h-3.5 text-muted transition-transform duration-200" id="course-switcher-chevron"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
|
||||
17
app/utils.py
17
app/utils.py
@@ -1,11 +1,12 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from cachetools import TTLCache, cached
|
||||
from eventlet.semaphore import Semaphore
|
||||
from flask import request, session, redirect, url_for, current_app, abort, make_response
|
||||
from typing import cast
|
||||
from itsdangerous import BadSignature, URLSafeSerializer
|
||||
from xkcdpass import xkcd_password
|
||||
from functools import wraps, lru_cache
|
||||
from functools import wraps
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
from .models import Deck, InfoBanner, Token, db, Course
|
||||
@@ -13,7 +14,8 @@ from .models import Deck, InfoBanner, Token, db, Course
|
||||
|
||||
# Create a module-level logger per Flask/Python standards
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_deck_cache = TTLCache(maxsize=1000, ttl=10)
|
||||
_deck_cache_lock = Semaphore()
|
||||
|
||||
|
||||
def init_password_generator():
|
||||
@@ -190,12 +192,14 @@ def token_required(f):
|
||||
return decorated_function
|
||||
|
||||
|
||||
# Cache the last 1000 lookups per worker.
|
||||
@lru_cache(maxsize=1000)
|
||||
|
||||
|
||||
|
||||
@cached(_deck_cache, lock=_deck_cache_lock)
|
||||
def get_cached_deck_info(token_id, course_folder, deck_slug):
|
||||
"""
|
||||
Returns a dictionary of primitive types for route authorization and routing.
|
||||
Never return raw SQLAlchemy models from an lru_cache!
|
||||
Never return raw SQLAlchemy models from a cache!
|
||||
"""
|
||||
# 1. Fetch Token
|
||||
token = db.session.get(Token, token_id)
|
||||
@@ -221,7 +225,6 @@ def get_cached_deck_info(token_id, course_folder, deck_slug):
|
||||
# 4. Check Authorization
|
||||
is_unlocked = deck_slug in [d.deck for d in token.decks]
|
||||
|
||||
# Return only primitive data (bools, strings) so the cache is thread-safe!
|
||||
return {
|
||||
"auth": is_unlocked,
|
||||
"course_exists": True,
|
||||
|
||||
@@ -5,6 +5,7 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"cachetools>=6.2.6",
|
||||
"eventlet>=0.40.0",
|
||||
"flask>=3.1.1",
|
||||
"flask-bcrypt>=1.0.1",
|
||||
@@ -14,6 +15,7 @@ dependencies = [
|
||||
"flask-wtf>=1.2.2",
|
||||
"gunicorn>=23.0.0",
|
||||
"locust>=2.34.0",
|
||||
"markdown>=3.9",
|
||||
"psycopg2-binary>=2.9.11",
|
||||
"pytz>=2025.2",
|
||||
"redis>=7.0.1",
|
||||
|
||||
57
uv.lock
generated
57
uv.lock
generated
@@ -171,6 +171,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/df/e7c780e463ee7bd7951770692bbea5a605f56b9809ec7f6ce751d7b2ee88/brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937", size = 369008, upload-time = "2025-11-05T18:39:41.515Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "6.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "7.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/07/56595285564e90777d758ebd383d6b0b971b87729bbe2184a849932a3736/cachetools-7.0.1.tar.gz", hash = "sha256:e31e579d2c5b6e2944177a0397150d312888ddf4e16e12f1016068f0c03b8341", size = 36126, upload-time = "2026-02-10T22:24:05.03Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/9e/5faefbf9db1db466d633735faceda1f94aa99ce506ac450d232536266b32/cachetools-7.0.1-py3-none-any.whl", hash = "sha256:8f086515c254d5664ae2146d14fc7f65c9a4bce75152eb247e5a9c5e6d7b2ecf", size = 13484, upload-time = "2026-02-10T22:24:03.741Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
@@ -930,6 +954,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/d2/dc5379876d3a481720803653ea4d219f0c26f2d2b37c9243baaa16d0bc79/locust-2.43.3-py3-none-any.whl", hash = "sha256:e032c119b54a9d984cb74a936ee83cfd7d68b3c76c8f308af63d04f11396b553", size = 1463473, upload-time = "2026-02-12T09:55:31.727Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
@@ -1569,6 +1620,8 @@ name = "tutor-tool"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cachetools", version = "6.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "cachetools", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "eventlet" },
|
||||
{ name = "flask" },
|
||||
{ name = "flask-bcrypt" },
|
||||
@@ -1579,6 +1632,8 @@ dependencies = [
|
||||
{ name = "gunicorn" },
|
||||
{ name = "locust", version = "2.34.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "locust", version = "2.43.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "pytz" },
|
||||
{ name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@@ -1588,6 +1643,7 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "cachetools", specifier = ">=6.2.6" },
|
||||
{ name = "eventlet", specifier = ">=0.40.0" },
|
||||
{ name = "flask", specifier = ">=3.1.1" },
|
||||
{ name = "flask-bcrypt", specifier = ">=1.0.1" },
|
||||
@@ -1597,6 +1653,7 @@ requires-dist = [
|
||||
{ name = "flask-wtf", specifier = ">=1.2.2" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
{ name = "locust", specifier = ">=2.34.0" },
|
||||
{ name = "markdown", specifier = ">=3.9" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
||||
{ name = "pytz", specifier = ">=2025.2" },
|
||||
{ name = "redis", specifier = ">=7.0.1" },
|
||||
|
||||
Reference in New Issue
Block a user