diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag
index a791d05dd..02cb0aadd 100644
--- a/src/static/riot/competitions/detail/_tabs.tag
+++ b/src/static/riot/competitions/detail/_tabs.tag
@@ -583,10 +583,6 @@
.phase-info
margin-bottom 10px
- .admin-tab
- margin 0 auto
- width 100%
-
pre
background #f4f4f4
border 1px solid #ddd
diff --git a/src/static/riot/competitions/detail/detail.tag b/src/static/riot/competitions/detail/detail.tag
index f971675b3..7bed78b29 100644
--- a/src/static/riot/competitions/detail/detail.tag
+++ b/src/static/riot/competitions/detail/detail.tag
@@ -645,4 +645,4 @@
}
}
-
+
\ No newline at end of file
diff --git a/src/static/riot/competitions/detail/leaderboards.tag b/src/static/riot/competitions/detail/leaderboards.tag
index 80af465c4..aabc566f5 100644
--- a/src/static/riot/competitions/detail/leaderboards.tag
+++ b/src/static/riot/competitions/detail/leaderboards.tag
@@ -63,7 +63,9 @@
{ pretty_date(submission.created_when) }
{submission.id}
-
+
@@ -227,6 +229,18 @@
return dt.isValid ? dt.toMillis() : 0
}
+ self.get_score_sort_value = function(column, submission) {
+ if (column.task_id === -1) {
+ let value = _.get(submission, 'fact_sheet_answers[' + column.key + ']')
+ return (value !== null && typeof value !== 'undefined' && value !== '') ? value : ''
+ }
+ let score = _.get(_.find(submission.scores, {
+ task_id: column.task_id,
+ column_key: column.key
+ }), 'score')
+ return (score !== null && typeof score !== 'undefined' && score !== '') ? score : ''
+ }
+
self.bold_class = function(column, submission){
return_class = ''
if(column.task_id != -1){
@@ -245,7 +259,7 @@
return _.get(submission, 'fact_sheet_answers[' + column.key + ']', 'n/a')
} else {
let score = _.get(_.find(submission.scores, {'task_id': column.task_id, 'column_key': column.key}), 'score')
- if (score) {
+ if (score !== null && typeof score !== 'undefined' && score !== '') {
return score
}
}
diff --git a/src/static/riot/competitions/detail/submission_manager.tag b/src/static/riot/competitions/detail/submission_manager.tag
index 87c4c0c9f..441a6cf52 100644
--- a/src/static/riot/competitions/detail/submission_manager.tag
+++ b/src/static/riot/competitions/detail/submission_manager.tag
@@ -232,7 +232,6 @@
Task {i + 1}
- Admin
ERROR: Submission is a parent, but has no children. There was an error
@@ -248,9 +247,6 @@
-
-
-
@@ -713,9 +709,6 @@
self.update()
})
}
- if (opts.admin) {
- submission.admin = true
- }
self.selected_submission = submission
self.update()
$(self.refs.modal)
diff --git a/src/static/riot/competitions/detail/submission_modal.tag b/src/static/riot/competitions/detail/submission_modal.tag
index 936ce4617..575ccb8d7 100644
--- a/src/static/riot/competitions/detail/submission_modal.tag
+++ b/src/static/riot/competitions/detail/submission_modal.tag
@@ -3,7 +3,6 @@
DOWNLOADS
LOGS
VISUALIZATION
- ADMIN
FACT SHEET ANSWERS
@@ -225,10 +224,6 @@
max-height 465px
overflow auto
- .leaderboard-tab
- height 515px
- overflow auto
-
.modal-tab
height 530px
diff --git a/src/static/riot/competitions/detail/submission_scores.tag b/src/static/riot/competitions/detail/submission_scores.tag
deleted file mode 100644
index 6cf69730a..000000000
--- a/src/static/riot/competitions/detail/submission_scores.tag
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/static/riot/competitions/detail/worker-monitor-toggle.tag b/src/static/riot/competitions/detail/worker-monitor-toggle.tag
new file mode 100644
index 000000000..d31b64e2e
--- /dev/null
+++ b/src/static/riot/competitions/detail/worker-monitor-toggle.tag
@@ -0,0 +1,650 @@
+
+
+ { showWorkersPanel ? 'Hide workers' : 'Show workers' }
+
+
+
+
+
+
+
+
+ Compute Workers
+
+ { connectionLabel() }
+ •
+ Last update: { lastSyncLabel() }
+
+
+
+
Drag to move
+
+
+
+
+
{ totalCount() }
+
Total
+
+
+
{ availableCount() }
+
Available
+
+
+
{ busyCount() }
+
Busy
+
+
+
{ unavailableCount() }
+
Down
+
+
+
+
+
+
+
+
+
+ Worker
+ Status
+ Jobs
+ Last seen
+
+
+
+
+
+ { worker.hostname }
+
+
+
+
+ { getStatusText(worker) }
+
+
+ { worker.running_jobs || 0 }
+ { formatLastSeen(worker.timestamp) }
+
+
+
+
+
+
Waiting for the first websocket snapshot.
+
+
+
+
+
+
+
+
+
+
Private queues
+
+
+
+
+
+ Worker
+ Status
+ Jobs
+ Last seen
+
+
+
+
+
+ { worker.hostname || worker.competition_title || 'Private competition' }
+
+ { worker.queue_name || '—' }
+
+
+
+ { getStatusText(worker) }
+
+
+ { worker.running_jobs || 0 }
+ { formatLastSeen(worker.timestamp) }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag
index a9c318c28..1cb9ebeb5 100644
--- a/src/static/riot/competitions/editor/_phases.tag
+++ b/src/static/riot/competitions/editor/_phases.tag
@@ -115,7 +115,7 @@
- Tasks (Order will be saved) Note: Adding a new task will cause all submissions to be run against it.
+ Tasks (Order will be saved)
@@ -261,6 +261,24 @@
// awesome markdown editor
self.simple_markdown_editor = create_easyMDE(self.refs.description)
+ // Custom menu template that renders description under the item name
+ // data-text is set to name-only so that selected labels show only the title
+ var dropdown_menu_template = function(response) {
+ var html = ''
+ $.each(response.values, function(index, item) {
+ let name = item.name || ''
+ let value = item.value || ''
+ let description = item.description
+ html += '
'
+ html += '
' + name + ' '
+ if (description) {
+ html += '
' + description + '
'
+ }
+ html += '
'
+ })
+ return html
+ }
+
// semantic multiselect
$(self.refs.multiselect).dropdown({
apiSettings: {
@@ -270,6 +288,7 @@
return {success: true, results: _.values(data.results)}
},
},
+ templates: {menu: dropdown_menu_template},
onAdd: self.task_added,
onRemove: self.task_removed,
})
@@ -282,10 +301,11 @@
return {success: true, results: _.values(data.results)}
},
},
+ templates: {menu: dropdown_menu_template},
onAdd: self.public_data_added,
onRemove: self.public_data_removed,
})
-
+
$(self.refs.starting_kit_multiselect).dropdown({
apiSettings: {
url: `${URLS.API}datasets/?search={query}&type=starting_kit`,
@@ -294,6 +314,7 @@
return {success: true, results: _.values(data.results)}
},
},
+ templates: {menu: dropdown_menu_template},
onAdd: self.starting_kit_added,
onRemove: self.starting_kit_removed,
})
@@ -858,5 +879,11 @@
diff --git a/src/templates/403.html b/src/templates/403.html
new file mode 100644
index 000000000..293ba7199
--- /dev/null
+++ b/src/templates/403.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+
+{% block content %}
+
+
+
You do not have permission to access this page.
+
If you believe this is an error, please fill a bug report . For more information about Codabench, check out the docs .
+
Return to Home
+
+{% endblock %}
diff --git a/src/templates/404.html b/src/templates/404.html
index eaa3948d5..9ab55820d 100644
--- a/src/templates/404.html
+++ b/src/templates/404.html
@@ -6,7 +6,7 @@
Could not find the page you requested.
If you are the challenge organizer and this URL previously pointed to a valid competition and it is now missing please contact us at info@codabench.org for more information.
-
If this is an error, please file a bug report . For more information about Codabench, check out the docs .
+
If this is an error, please fill a bug report . For more information about Codabench, check out the docs .
Return to Home
{% endblock %}
diff --git a/src/templates/500.html b/src/templates/500.html
new file mode 100644
index 000000000..39410a45e
--- /dev/null
+++ b/src/templates/500.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+
+{% block content %}
+
+
+
Something went wrong on our end.
+
If this is an error, please fill a bug report . For more information about Codabench, check out the docs .
+
Return to Home
+
+{% endblock %}
diff --git a/src/templates/competitions/detail.html b/src/templates/competitions/detail.html
index 43a2228db..8e011f45f 100644
--- a/src/templates/competitions/detail.html
+++ b/src/templates/competitions/detail.html
@@ -1,7 +1,11 @@
{% extends "base.html" %}
-{% block title %}{{ competition.title }} - Codabench{% endblock %}
+{% block title %}{{ competition.title }} – Codabench{% endblock %}
{% block content %}
-
+
+
{% endblock %}
\ No newline at end of file
diff --git a/src/urls.py b/src/urls.py
index 2634d19d6..88013d5a7 100644
--- a/src/urls.py
+++ b/src/urls.py
@@ -33,7 +33,6 @@
]
-
if settings.DEBUG:
# Static files for local dev, so we don't have to collectstatic and such
urlpatterns += staticfiles_urlpatterns()
diff --git a/src/utils/consumers.py b/src/utils/consumers.py
new file mode 100644
index 000000000..6d076bac9
--- /dev/null
+++ b/src/utils/consumers.py
@@ -0,0 +1,142 @@
+import asyncio
+import json
+import logging
+import time
+
+from competitions.models import Competition
+
+from asgiref.sync import sync_to_async
+from channels.generic.websocket import AsyncJsonWebsocketConsumer
+from django_redis import get_redis_connection
+
+from utils.worker_utils import WORKER_HEARTBEAT_TTL, WORKERS_REGISTRY_KEY
+
+logger = logging.getLogger(__name__)
+
+r = get_redis_connection("default")
+
+
+def _load_snapshot(competition_queue_name=None):
+ """
+ Charge les workers depuis Redis.
+ - workers par défaut : toujours inclus (queue_source == 'default')
+ - workers privés : inclus uniquement si leur queue_source correspond
+ à la queue de la compétition courante
+ """
+ raw = r.hgetall(WORKERS_REGISTRY_KEY)
+ workers = []
+ private_workers = []
+ now = time.time()
+
+ for _, value in raw.items():
+ try:
+ worker = json.loads(value)
+ except Exception:
+ continue
+
+ if now - worker.get("last_seen", 0) > WORKER_HEARTBEAT_TTL:
+ continue
+
+ if worker.get("queue_source") == "default":
+ workers.append(worker)
+ else:
+ # Worker privé : n'afficher que si la queue correspond à la compétition
+ if competition_queue_name and worker.get("queue_source") == competition_queue_name:
+ private_workers.append(worker)
+
+ workers.sort(key=lambda x: x.get("hostname", ""))
+ private_workers.sort(key=lambda x: (x.get("queue_source", ""), x.get("hostname", "")))
+ return workers, private_workers
+
+
+def _get_competition_queue_name(competition_id):
+ """Retourne le nom de la queue de la compétition, ou None."""
+ if not competition_id:
+ return None
+ try:
+ competition = Competition.objects.select_related("queue").get(pk=competition_id)
+ if competition.queue and competition.queue.name:
+ return competition.queue.name
+ except Exception:
+ logger.warning("Competition %s not found or has no queue", competition_id)
+ return None
+
+
+class ComputeWorkersConsumer(AsyncJsonWebsocketConsumer):
+
+ async def connect(self):
+ user = self.scope.get("user")
+ if user is None or user.is_anonymous:
+ await self.close()
+ return
+ await self.accept()
+ await self.channel_layer.group_add("compute_workers", self.channel_name)
+ self._competition_queue_name = None
+ self._running = True
+ self._subscribed = asyncio.Event()
+ self._task = asyncio.create_task(self._push_workers_loop())
+
+ async def disconnect(self, close_code):
+ self._running = False
+ await self.channel_layer.group_discard("compute_workers", self.channel_name)
+ task = getattr(self, "_task", None)
+ if task:
+ task.cancel()
+ try:
+ await task
+ except (asyncio.CancelledError, RuntimeError):
+ pass
+
+ async def receive_json(self, content):
+ logger.debug("WebSocket received: %s", content)
+ if content.get("type") == "subscribe":
+ competition_id = content.get("competition_id")
+ self._competition_queue_name = await sync_to_async(_get_competition_queue_name)(
+ competition_id)
+ self._subscribed.set()
+
+ async def _push_workers_loop(self):
+ try:
+ try:
+ await asyncio.wait_for(self._subscribed.wait(), timeout=5.0)
+ except asyncio.TimeoutError:
+ logger.warning("WebSocket subscribe timeout, proceeding without competition filter")
+
+ while self._running:
+ workers, private_workers = await sync_to_async(_load_snapshot)(
+ self._competition_queue_name
+ )
+ if not self._running:
+ break
+ try:
+ await self.send_json({
+ "type": "workers.snapshot",
+ "workers": workers,
+ "private_workers": private_workers,
+ })
+ except RuntimeError:
+ break
+ await asyncio.sleep(3)
+ except asyncio.CancelledError:
+ pass
+
+ async def worker_health(self, event):
+ worker = event["worker"]
+ is_default = worker.get("queue_source") == "default"
+ is_mine = (
+ self._competition_queue_name is not None
+ and worker.get("queue_source") == self._competition_queue_name
+ )
+ if not is_default and not is_mine:
+ return
+ try:
+ workers, private_workers = await sync_to_async(_load_snapshot)(
+ self._competition_queue_name
+ )
+ await self.send_json({
+ "type": "workers.snapshot",
+ "workers": workers,
+ "private_workers": private_workers,
+ })
+ except RuntimeError:
+ pass
diff --git a/src/utils/worker_utils.py b/src/utils/worker_utils.py
new file mode 100644
index 000000000..f1b9a474b
--- /dev/null
+++ b/src/utils/worker_utils.py
@@ -0,0 +1,28 @@
+from queues.models import Queue
+
+WORKERS_REGISTRY_KEY = "workers:registry"
+WORKER_HEARTBEAT_TTL = 180
+
+
+def extract_queue_names(active_queues):
+ names = set()
+ for q in active_queues or []:
+ if isinstance(q, dict) and q.get("name"):
+ names.add(q["name"])
+ return names
+
+
+def known_compute_queue_names():
+ return set(
+ Queue.objects.exclude(name__isnull=True)
+ .exclude(name="")
+ .values_list("name", flat=True)
+ )
+
+
+def is_compute_worker(worker_name, queue_names, known_queue_names):
+ return (
+ bool(queue_names & known_queue_names)
+ or "compute-worker" in queue_names
+ or worker_name.startswith("compute-worker")
+ )
diff --git a/uv.lock b/uv.lock
index 41ca175d6..486257380 100644
--- a/uv.lock
+++ b/uv.lock
@@ -138,6 +138,28 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" },
]
+[[package]]
+name = "black"
+version = "26.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "mypy-extensions" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "platformdirs" },
+ { name = "pytokens" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" },
+ { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" },
+ { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" },
+ { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" },
+]
+
[[package]]
name = "blessed"
version = "1.38.0"
@@ -371,6 +393,7 @@ dependencies = [
{ name = "argh" },
{ name = "azure-storage-blob" },
{ name = "azure-storage-common" },
+ { name = "black" },
{ name = "blessings" },
{ name = "boto3" },
{ name = "botocore" },
@@ -412,6 +435,7 @@ dependencies = [
{ name = "python-dateutil" },
{ name = "pytz" },
{ name = "pyyaml" },
+ { name = "redis-cli" },
{ name = "requests" },
{ name = "s3transfer" },
{ name = "setuptools" },
@@ -441,6 +465,7 @@ requires-dist = [
{ name = "argh", specifier = "==0.31.3" },
{ name = "azure-storage-blob", specifier = ">=12,<13" },
{ name = "azure-storage-common", specifier = "==2.1.0" },
+ { name = "black", specifier = ">=26.3.1" },
{ name = "blessings", specifier = "==1.7" },
{ name = "boto3", specifier = "==1.42.50" },
{ name = "botocore", specifier = "==1.42.50" },
@@ -450,7 +475,7 @@ requires-dist = [
{ name = "channels-redis", specifier = "==4.0.0" },
{ name = "configobj", specifier = "==5.0.9" },
{ name = "dj-database-url", specifier = "==0.4.2" },
- { name = "django", specifier = "==5.2.13" },
+ { name = "django", specifier = "==5.2.14" },
{ name = "django-ajax-selects", specifier = "==3.0.3" },
{ name = "django-cors-headers", specifier = "==4.9.0" },
{ name = "django-enforce-host", specifier = "==1.1.0" },
@@ -482,6 +507,7 @@ requires-dist = [
{ name = "python-dateutil", specifier = "==2.9.0" },
{ name = "pytz", specifier = ">=2025.2" },
{ name = "pyyaml", specifier = "==6.0.3" },
+ { name = "redis-cli", specifier = ">=1.0.1" },
{ name = "requests", specifier = "==2.33.1" },
{ name = "s3transfer", specifier = "==0.16.0" },
{ name = "setuptools", specifier = "==82.0.0" },
@@ -489,7 +515,7 @@ requires-dist = [
{ name = "social-auth-core", specifier = "==4.8.5" },
{ name = "twisted", specifier = "==25.5.0" },
{ name = "tzdata", specifier = ">=2025.3" },
- { name = "urllib3", specifier = "==2.6.3" },
+ { name = "urllib3", specifier = "==2.7.0" },
{ name = "uvicorn", specifier = "==0.38" },
{ name = "watchdog", specifier = "==6.0.0" },
{ name = "websockets", specifier = "==16.0.0" },
@@ -635,16 +661,16 @@ wheels = [
[[package]]
name = "django"
-version = "5.2.13"
+version = "5.2.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1f/c5/c69e338eb2959f641045802e5ea87ca4bf5ac90c5fd08953ca10742fad51/django-5.2.13.tar.gz", hash = "sha256:a31589db5188d074c63f0945c3888fad104627dfcc236fb2b97f71f89da33bc4", size = 10890368, upload-time = "2026-04-07T14:02:15.072Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/95/95f7faa0950867afaa0bef2460c6263afd6a2c78cc9434046ed28160b015/django-5.2.14.tar.gz", hash = "sha256:58a63ba841662e5c686b57ba1fec52ddd68c0b93bd96ac3029d55728f00bf8a2", size = 10895118, upload-time = "2026-05-05T13:57:31.104Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/59/b1/51ab36b2eefcf8cdb9338c7188668a157e29e30306bfc98a379704c9e10d/django-5.2.13-py3-none-any.whl", hash = "sha256:5788fce61da23788a8ce6f02583765ab060d396720924789f97fa42119d37f7a", size = 8310982, upload-time = "2026-04-07T14:02:08.883Z" },
+ { url = "https://files.pythonhosted.org/packages/14/44/f172870cf87aa25afef48fb72adba89ee8b77fcab6f3b23d240b923f1528/django-5.2.14-py3-none-any.whl", hash = "sha256:6f712143bd3064310d1f50fac859c3e9a274bdcfc9595339853be7779297fc76", size = 8311320, upload-time = "2026-05-05T13:57:25.795Z" },
]
[[package]]
@@ -981,11 +1007,11 @@ wheels = [
[[package]]
name = "idna"
-version = "3.13"
+version = "3.15"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
]
[[package]]
@@ -1272,6 +1298,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" },
]
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
[[package]]
name = "nh3"
version = "0.3.3"
@@ -1335,6 +1370,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" },
]
+[[package]]
+name = "pathspec"
+version = "1.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
+]
+
[[package]]
name = "pexpect"
version = "4.9.0"
@@ -1380,6 +1424,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
]
+[[package]]
+name = "platformdirs"
+version = "4.9.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" },
+]
+
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -1552,6 +1605,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/a5/c6ba13860bdf5525f1ab01e01cc667578d6f1efc8a1dba355700fb04c29b/python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b", size = 133681, upload-time = "2020-06-29T12:15:47.502Z" },
]
+[[package]]
+name = "pytokens"
+version = "0.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" },
+ { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
+]
+
[[package]]
name = "pytz"
version = "2026.1.post1"
@@ -1597,6 +1664,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" },
]
+[[package]]
+name = "redis-cli"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "redis" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/7a/464077bce2a14cd8399776ae0c694e7c240b58ffe4b231413013f2fb48fe/redis-cli-1.0.1.tar.gz", hash = "sha256:6be46d8e6ae638fec27cb425d499b7f25b1592402c74d5d17f5b85b5e7f988a0", size = 5958, upload-time = "2023-10-02T05:58:36.407Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/29/33cbdafefd22613cc003f837b6f0295462b2e91696180f06c5210f576a1b/redis_cli-1.0.1-py3-none-any.whl", hash = "sha256:e9f6af4a8b7591d8bfd1e23be89bdd9666ce824df881748fb02b88dd30575dc8", size = 5851, upload-time = "2023-10-02T05:58:32.924Z" },
+]
+
[[package]]
name = "referencing"
version = "0.37.0"
@@ -1841,11 +1920,11 @@ wheels = [
[[package]]
name = "urllib3"
-version = "2.6.3"
+version = "2.7.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
]
[[package]]
diff --git a/version.json b/version.json
index c42192490..60297635a 100644
--- a/version.json
+++ b/version.json
@@ -1,5 +1,5 @@
{
- "tag_name": "v1.26",
- "release_name": "v1.26",
- "html_url": "https://github.com/codalab/codabench/releases/tag/v1.26"
+ "tag_name": "v1.27",
+ "release_name": "v1.27",
+ "html_url": "https://github.com/codalab/codabench/releases/tag/v1.27"
}