From dd1bcdb8dc235b2010f89f0c87e4695cd69589d1 Mon Sep 17 00:00:00 2001
From: Johnny Liu
Date: Tue, 28 May 2024 11:19:30 -0400
Subject: [PATCH 1/6] create code for ranked games
---
Pipfile | 4 +-
Pipfile.lock | 619 +++++++++++-------
manage.py | 2 +
.../0008_user_is_gauntlet_running.py | 18 +
.../migrations/0009_user_last_gauntlet_run.py | 18 +
othello/apps/auth/models.py | 4 +
.../migrations/0031_auto_20240523_1555.py | 23 +
.../migrations/0032_submission_gauntlet.py | 18 +
.../games/migrations/0033_game_is_ranked.py | 18 +
.../migrations/0034_auto_20240525_2248.py | 22 +
.../migrations/0035_auto_20240526_1514.py | 23 +
.../games/migrations/0036_game_is_gauntlet.py | 18 +
othello/apps/games/models.py | 23 +-
othello/apps/games/tasks.py | 173 ++---
othello/apps/rating/__init__.py | 2 +
othello/apps/rating/admin.py | 0
othello/apps/rating/apps.py | 6 +
othello/apps/rating/forms.py | 12 +
.../apps/rating/migrations/0001_initial.py | 32 +
.../migrations/0002_auto_20240525_1335.py | 30 +
.../migrations/0003_auto_20240525_2011.py | 28 +
.../migrations/0004_alter_gauntlet_myside1.py | 18 +
.../migrations/0005_auto_20240526_1510.py | 28 +
.../migrations/0006_alter_gauntlet_myside2.py | 18 +
.../migrations/0007_auto_20240526_1512.py | 23 +
.../migrations/0008_auto_20240526_1514.py | 23 +
.../migrations/0009_auto_20240526_1536.py | 33 +
.../migrations/0010_alter_gauntlet_myside3.py | 18 +
.../migrations/0011_auto_20240527_2119.py | 33 +
.../migrations/0012_gauntlet_pastrating.py | 18 +
.../0013_gauntlet_celery_task_id.py | 18 +
.../migrations/0014_alter_gauntlet_myside2.py | 18 +
.../migrations/0015_auto_20240528_0833.py | 23 +
othello/apps/rating/migrations/__init__.py | 0
othello/apps/rating/models.py | 76 +++
othello/apps/rating/tasks.py | 292 +++++++++
othello/apps/rating/templatetags/__init__.py | 1 +
othello/apps/rating/templatetags/extras.py | 28 +
othello/apps/rating/urls.py | 14 +
othello/apps/rating/views.py | 241 +++++++
othello/moderator/runners.py | 128 +++-
othello/sandboxing/__init__.py | 7 +-
othello/sandboxing/import_wrapper.py | 2 +-
othello/settings/__init__.py | 1 +
othello/settings/secret.sample.py | 2 +-
othello/static/js/base.js | 6 +
othello/templates/base.html | 14 +
othello/templates/games/watch_list.html | 6 +
othello/templates/rating/gauntlet.html | 40 ++
othello/templates/rating/gauntletrunning.html | 28 +
othello/templates/rating/help.html | 45 ++
othello/templates/rating/history.html | 40 ++
othello/templates/rating/manage.html | 28 +
othello/templates/rating/standings.html | 44 ++
othello/templates/rating/throttle.html | 9 +
othello/urls.py | 1 +
56 files changed, 2062 insertions(+), 355 deletions(-)
create mode 100644 othello/apps/auth/migrations/0008_user_is_gauntlet_running.py
create mode 100644 othello/apps/auth/migrations/0009_user_last_gauntlet_run.py
create mode 100644 othello/apps/games/migrations/0031_auto_20240523_1555.py
create mode 100644 othello/apps/games/migrations/0032_submission_gauntlet.py
create mode 100644 othello/apps/games/migrations/0033_game_is_ranked.py
create mode 100644 othello/apps/games/migrations/0034_auto_20240525_2248.py
create mode 100644 othello/apps/games/migrations/0035_auto_20240526_1514.py
create mode 100644 othello/apps/games/migrations/0036_game_is_gauntlet.py
create mode 100644 othello/apps/rating/__init__.py
create mode 100644 othello/apps/rating/admin.py
create mode 100644 othello/apps/rating/apps.py
create mode 100644 othello/apps/rating/forms.py
create mode 100644 othello/apps/rating/migrations/0001_initial.py
create mode 100644 othello/apps/rating/migrations/0002_auto_20240525_1335.py
create mode 100644 othello/apps/rating/migrations/0003_auto_20240525_2011.py
create mode 100644 othello/apps/rating/migrations/0004_alter_gauntlet_myside1.py
create mode 100644 othello/apps/rating/migrations/0005_auto_20240526_1510.py
create mode 100644 othello/apps/rating/migrations/0006_alter_gauntlet_myside2.py
create mode 100644 othello/apps/rating/migrations/0007_auto_20240526_1512.py
create mode 100644 othello/apps/rating/migrations/0008_auto_20240526_1514.py
create mode 100644 othello/apps/rating/migrations/0009_auto_20240526_1536.py
create mode 100644 othello/apps/rating/migrations/0010_alter_gauntlet_myside3.py
create mode 100644 othello/apps/rating/migrations/0011_auto_20240527_2119.py
create mode 100644 othello/apps/rating/migrations/0012_gauntlet_pastrating.py
create mode 100644 othello/apps/rating/migrations/0013_gauntlet_celery_task_id.py
create mode 100644 othello/apps/rating/migrations/0014_alter_gauntlet_myside2.py
create mode 100644 othello/apps/rating/migrations/0015_auto_20240528_0833.py
create mode 100644 othello/apps/rating/migrations/__init__.py
create mode 100644 othello/apps/rating/models.py
create mode 100644 othello/apps/rating/tasks.py
create mode 100644 othello/apps/rating/templatetags/__init__.py
create mode 100644 othello/apps/rating/templatetags/extras.py
create mode 100644 othello/apps/rating/urls.py
create mode 100644 othello/apps/rating/views.py
create mode 100644 othello/templates/rating/gauntlet.html
create mode 100644 othello/templates/rating/gauntletrunning.html
create mode 100644 othello/templates/rating/help.html
create mode 100644 othello/templates/rating/history.html
create mode 100644 othello/templates/rating/manage.html
create mode 100644 othello/templates/rating/standings.html
create mode 100644 othello/templates/rating/throttle.html
diff --git a/Pipfile b/Pipfile
index 162a175c1..e490cfa4d 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,7 +4,6 @@ verify_ssl = true
name = "pypi"
[packages]
-celery = "~=5.2.2"
channels = "~=3.0.3"
channels-redis = "~=3.2.0"
daphne = "~=3.0.1"
@@ -20,6 +19,9 @@ service_identity = "~=18.1.0"
social-auth-app-django = "~=4.0.0"
sqlparse = "~=0.4.2"
pyopenssl = "*"
+colorama = "*"
+celery = "==5.2.7"
+gevent = "*"
[dev-packages]
flake8 = "~=3.9.2"
diff --git a/Pipfile.lock b/Pipfile.lock
index 458489f39..d54850eb1 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "faf6dcb98cc0a6a1d4d6e3648d84b2ec8471ab6c9e258dc754027a5f0034389f"
+ "sha256": "7c3c92c539206eafe88f3e1102ba40fcf12eef38c3f55eeb6a339687e3ce29c3"
},
"pipfile-spec": 6,
"requires": {},
@@ -31,11 +31,11 @@
},
"asgiref": {
"hashes": [
- "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e",
- "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"
+ "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47",
+ "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"
],
- "markers": "python_version >= '3.7'",
- "version": "==3.7.2"
+ "markers": "python_version >= '3.8'",
+ "version": "==3.8.1"
},
"asttokens": {
"hashes": [
@@ -111,6 +111,7 @@
"sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
"sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
],
+ "markers": "python_version >= '3.7'",
"version": "==3.6.4.0"
},
"celery": {
@@ -185,7 +186,7 @@
"sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956",
"sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"
],
- "markers": "platform_python_implementation != 'PyPy'",
+ "markers": "platform_python_implementation == 'CPython' and sys_platform == 'win32'",
"version": "==1.16.0"
},
"channels": {
@@ -312,11 +313,11 @@
},
"click-didyoumean": {
"hashes": [
- "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
- "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
+ "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463",
+ "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c"
],
- "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'",
- "version": "==0.3.0"
+ "markers": "python_full_version >= '3.6.2'",
+ "version": "==0.3.1"
},
"click-plugins": {
"hashes": [
@@ -333,6 +334,15 @@
"markers": "python_version >= '3.6'",
"version": "==0.3.0"
},
+ "colorama": {
+ "hashes": [
+ "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
+ "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
+ "version": "==0.4.6"
+ },
"constantly": {
"hashes": [
"sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9",
@@ -343,41 +353,41 @@
},
"cryptography": {
"hashes": [
- "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129",
- "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe",
- "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20",
- "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec",
- "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3",
- "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd",
- "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5",
- "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b",
- "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46",
- "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504",
- "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306",
- "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead",
- "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e",
- "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938",
- "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a",
- "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b",
- "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a",
- "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd",
- "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54",
- "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c",
- "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857",
- "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f",
- "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f",
- "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef",
- "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c",
- "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548",
- "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65",
- "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4",
- "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4",
- "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a",
- "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151",
- "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"
+ "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55",
+ "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785",
+ "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b",
+ "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886",
+ "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82",
+ "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1",
+ "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda",
+ "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f",
+ "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68",
+ "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60",
+ "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7",
+ "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd",
+ "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582",
+ "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc",
+ "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858",
+ "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b",
+ "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2",
+ "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678",
+ "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13",
+ "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4",
+ "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8",
+ "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604",
+ "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477",
+ "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e",
+ "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a",
+ "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9",
+ "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14",
+ "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda",
+ "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da",
+ "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562",
+ "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2",
+ "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"
],
"markers": "python_version >= '3.7'",
- "version": "==42.0.3"
+ "version": "==42.0.7"
},
"daphne": {
"hashes": [
@@ -406,11 +416,12 @@
},
"django": {
"hashes": [
- "sha256:5dd5b787c3ba39637610fe700f54bf158e33560ea0dba600c19921e7ff926ec5",
- "sha256:aaee9fb0fb4ebd4311520887ad2e33313d368846607f82a9a0ed461cd4c35b18"
+ "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777",
+ "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"
],
+ "index": "pypi",
"markers": "python_version >= '3.6'",
- "version": "==3.2.24"
+ "version": "==3.2.25"
},
"django-celery-results": {
"hashes": [
@@ -437,6 +448,118 @@
"markers": "python_version >= '3.5'",
"version": "==2.0.1"
},
+ "gevent": {
+ "hashes": [
+ "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5",
+ "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de",
+ "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8",
+ "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5",
+ "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc",
+ "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800",
+ "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe",
+ "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7",
+ "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9",
+ "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533",
+ "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc",
+ "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056",
+ "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6",
+ "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026",
+ "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40",
+ "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07",
+ "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e",
+ "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be",
+ "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8",
+ "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5",
+ "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1",
+ "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789",
+ "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19",
+ "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5",
+ "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7",
+ "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388",
+ "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8",
+ "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98",
+ "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3",
+ "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7",
+ "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060",
+ "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d",
+ "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661",
+ "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c",
+ "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb",
+ "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f",
+ "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91",
+ "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0",
+ "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f",
+ "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836",
+ "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==24.2.1"
+ },
+ "greenlet": {
+ "hashes": [
+ "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67",
+ "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6",
+ "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257",
+ "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4",
+ "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676",
+ "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61",
+ "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc",
+ "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca",
+ "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7",
+ "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728",
+ "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305",
+ "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6",
+ "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379",
+ "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414",
+ "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04",
+ "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a",
+ "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf",
+ "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491",
+ "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559",
+ "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e",
+ "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274",
+ "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb",
+ "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b",
+ "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9",
+ "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b",
+ "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be",
+ "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506",
+ "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405",
+ "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113",
+ "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f",
+ "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5",
+ "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230",
+ "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d",
+ "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f",
+ "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a",
+ "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e",
+ "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61",
+ "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6",
+ "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d",
+ "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71",
+ "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22",
+ "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2",
+ "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3",
+ "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067",
+ "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc",
+ "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881",
+ "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3",
+ "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e",
+ "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac",
+ "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53",
+ "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0",
+ "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b",
+ "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83",
+ "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41",
+ "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c",
+ "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf",
+ "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da",
+ "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"
+ ],
+ "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'",
+ "version": "==3.0.3"
+ },
"hiredis": {
"hashes": [
"sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772",
@@ -561,11 +684,11 @@
},
"idna": {
"hashes": [
- "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
- "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
+ "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc",
+ "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"
],
"markers": "python_version >= '3.5'",
- "version": "==3.6"
+ "version": "==3.7"
},
"incremental": {
"hashes": [
@@ -593,81 +716,81 @@
},
"kombu": {
"hashes": [
- "sha256:0eac1bbb464afe6fb0924b21bf79460416d25d8abc52546d4f16cad94f789488",
- "sha256:30e470f1a6b49c70dc6f6d13c3e4cc4e178aa6c469ceb6bcd55645385fc84b93"
+ "sha256:011c4cd9a355c14a1de8d35d257314a1d2456d52b7140388561acac3cf1a97bf",
+ "sha256:5634c511926309c7f9789f1433e9ed402616b56836ef9878f01bd59267b4c7a9"
],
"markers": "python_version >= '3.8'",
- "version": "==5.3.5"
+ "version": "==5.3.7"
},
"matplotlib-inline": {
"hashes": [
- "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311",
- "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"
+ "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90",
+ "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"
],
- "markers": "python_version >= '3.5'",
- "version": "==0.1.6"
+ "markers": "python_version >= '3.8'",
+ "version": "==0.1.7"
},
"msgpack": {
"hashes": [
- "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862",
- "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d",
- "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3",
- "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672",
- "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0",
- "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9",
- "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee",
- "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46",
- "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524",
- "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819",
- "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc",
- "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc",
- "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1",
- "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82",
- "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81",
- "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6",
- "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d",
- "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2",
- "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c",
- "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87",
- "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84",
- "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e",
- "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95",
- "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f",
- "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b",
- "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93",
- "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf",
- "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61",
- "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c",
- "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8",
- "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d",
- "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c",
- "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4",
- "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba",
- "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415",
- "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee",
- "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d",
- "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9",
- "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075",
- "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f",
- "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7",
- "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681",
- "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329",
- "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1",
- "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf",
- "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c",
- "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5",
- "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b",
- "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5",
- "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e",
- "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b",
- "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad",
- "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd",
- "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7",
- "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002",
- "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"
+ "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982",
+ "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3",
+ "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40",
+ "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee",
+ "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693",
+ "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950",
+ "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151",
+ "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24",
+ "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305",
+ "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b",
+ "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c",
+ "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659",
+ "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d",
+ "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18",
+ "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746",
+ "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868",
+ "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2",
+ "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba",
+ "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228",
+ "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2",
+ "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273",
+ "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c",
+ "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653",
+ "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a",
+ "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596",
+ "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd",
+ "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8",
+ "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa",
+ "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85",
+ "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc",
+ "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836",
+ "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3",
+ "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58",
+ "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128",
+ "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db",
+ "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f",
+ "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77",
+ "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad",
+ "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13",
+ "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8",
+ "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b",
+ "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a",
+ "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543",
+ "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b",
+ "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce",
+ "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d",
+ "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a",
+ "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c",
+ "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f",
+ "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e",
+ "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011",
+ "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04",
+ "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480",
+ "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a",
+ "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d",
+ "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"
],
"markers": "python_version >= '3.8'",
- "version": "==1.0.7"
+ "version": "==1.0.8"
},
"oauthlib": {
"hashes": [
@@ -679,19 +802,11 @@
},
"parso": {
"hashes": [
- "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0",
- "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"
+ "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18",
+ "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"
],
"markers": "python_version >= '3.6'",
- "version": "==0.8.3"
- },
- "pexpect": {
- "hashes": [
- "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523",
- "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"
- ],
- "markers": "sys_platform != 'win32'",
- "version": "==4.9.0"
+ "version": "==0.8.4"
},
"pickleshare": {
"hashes": [
@@ -765,13 +880,6 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.6"
},
- "ptyprocess": {
- "hashes": [
- "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
- "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
- ],
- "version": "==0.7.0"
- },
"pure-eval": {
"hashes": [
"sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350",
@@ -781,35 +889,35 @@
},
"pyasn1": {
"hashes": [
- "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58",
- "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"
+ "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c",
+ "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==0.5.1"
+ "markers": "python_version >= '3.8'",
+ "version": "==0.6.0"
},
"pyasn1-modules": {
"hashes": [
- "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c",
- "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"
+ "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6",
+ "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==0.3.0"
+ "markers": "python_version >= '3.8'",
+ "version": "==0.4.0"
},
"pycparser": {
"hashes": [
- "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
- "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
+ "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
+ "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.21"
+ "markers": "python_version >= '3.8'",
+ "version": "==2.22"
},
"pygments": {
"hashes": [
- "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c",
- "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"
+ "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199",
+ "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"
],
- "markers": "python_version >= '3.7'",
- "version": "==2.17.2"
+ "markers": "python_version >= '3.8'",
+ "version": "==2.18.0"
},
"pyjwt": {
"hashes": [
@@ -821,12 +929,12 @@
},
"pyopenssl": {
"hashes": [
- "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf",
- "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"
+ "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad",
+ "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==24.0.0"
+ "version": "==24.1.0"
},
"python3-openid": {
"hashes": [
@@ -854,42 +962,44 @@
},
"requests": {
"hashes": [
- "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
- "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
+ "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289",
+ "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"
],
- "markers": "python_version >= '3.7'",
- "version": "==2.31.0"
+ "markers": "python_version >= '3.8'",
+ "version": "==2.32.2"
},
"requests-oauthlib": {
"hashes": [
- "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5",
- "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"
+ "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36",
+ "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==1.3.1"
+ "markers": "python_version >= '3.4'",
+ "version": "==2.0.0"
},
"sentry-sdk": {
"hashes": [
- "sha256:657abae98b0050a0316f0873d7149f951574ae6212f71d2e3a1c4c88f62d6456",
- "sha256:ac5cf56bb897ec47135d239ddeedf7c1c12d406fb031a4c0caa07399ed014d7e"
+ "sha256:139a71a19f5e9eb5d3623942491ce03cf8ebc14ea2e39ba3e6fe79560d8a5b1f",
+ "sha256:c5aeb095ba226391d337dd42a6f9470d86c9fc236ecc71cfc7cd1942b45010c6"
],
"index": "pypi",
- "version": "==1.40.4"
+ "markers": "python_version >= '3.6'",
+ "version": "==2.3.1"
},
"service-identity": {
"hashes": [
"sha256:001c0707759cb3de7e49c078a7c0c9cd12594161d3bf06b9c254fdcb1a60dc36",
"sha256:0858a54aabc5b459d1aafa8a518ed2081a285087f349fe3e55197989232e2e2d"
],
+ "index": "pypi",
"version": "==18.1.0"
},
"setuptools": {
"hashes": [
- "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401",
- "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"
+ "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
+ "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
],
"markers": "python_version >= '3.8'",
- "version": "==69.1.0"
+ "version": "==70.0.0"
},
"six": {
"hashes": [
@@ -910,11 +1020,11 @@
},
"social-auth-core": {
"hashes": [
- "sha256:8d16e66eb97bb7be43a023d6efa16628cdc94cefd8d8053930c98a0f676867e7",
- "sha256:9d9b51b7ce2ccd0b7139e6b7f52a32cb922726de819fb13babe35f12ae89852a"
+ "sha256:33cf970a623c442376f9d4a86fb187579e4438649daa5b5be993d05e74d7b2db",
+ "sha256:d3dbeb0999ffd0e68aa4bd73f2ac698a18133fd11b3fc890e1366f18c8889fac"
],
"markers": "python_version >= '3.8'",
- "version": "==4.5.3"
+ "version": "==4.5.4"
},
"sqlparse": {
"hashes": [
@@ -934,22 +1044,47 @@
},
"traitlets": {
"hashes": [
- "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74",
- "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"
+ "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7",
+ "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"
],
"markers": "python_version >= '3.8'",
- "version": "==5.14.1"
+ "version": "==5.14.3"
},
"twisted": {
"extras": [
"tls"
],
"hashes": [
- "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444",
- "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"
+ "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63",
+ "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"
],
"markers": "python_full_version >= '3.8.0'",
- "version": "==23.10.0"
+ "version": "==24.3.0"
+ },
+ "twisted-iocpsupport": {
+ "hashes": [
+ "sha256:0058c963c8957bcd3deda62122e89953c9de1e867a274facc9b15dde1a9f31e8",
+ "sha256:0c1b5cf37f0b2d96cc3c9bc86fff16613b9f5d0ca565c96cf1f1fb8cfca4b81c",
+ "sha256:196f7c7ccad4ba4d1783b1c4e1d1b22d93c04275cd780bf7498d16c77319ad6e",
+ "sha256:300437af17396a945a58dcfffd77863303a8b6d9e65c6e81f1d2eed55b50d444",
+ "sha256:391ac4d6002a80e15f35adc4ad6056f4fe1c17ceb0d1f98ba01b0f4f917adfd7",
+ "sha256:3c5dc11d72519e55f727320e3cee535feedfaee09c0f0765ed1ca7badff1ab3c",
+ "sha256:3d306fc4d88a6bcf61ce9d572c738b918578121bfd72891625fab314549024b5",
+ "sha256:4574eef1f3bb81501fb02f911298af3c02fe8179c31a33b361dd49180c3e644d",
+ "sha256:4e5f97bcbabdd79cbaa969b63439b89801ea560f11d42b0a387634275c633623",
+ "sha256:6081bd7c2f4fcf9b383dcdb3b3385d75a26a7c9d2be25b6950c3d8ea652d2d2d",
+ "sha256:76f7e67cec1f1d097d1f4ed7de41be3d74546e1a4ede0c7d56e775c4dce5dfb0",
+ "sha256:7c66fa0aa4236b27b3c61cb488662d85dae746a6d1c7b0d91cf7aae118445adf",
+ "sha256:858096c0d15e33f15ac157f455d8f86f2f2cdd223963e58c0f682a3af8362d89",
+ "sha256:872747a3b64e2909aee59c803ccd0bceb9b75bf27915520ebd32d69687040fa2",
+ "sha256:afa2b630797f9ed2f27f3d9f55e3f72b4244911e45a8c82756f44babbf0b243e",
+ "sha256:c2712b778bacf1db434e3e065adfed3db300754186a29aecac1efae9ef4bcaff",
+ "sha256:c27985e949b9b1a1fb4c20c71d315c10ea0f93fdf3ccdd4a8c158b5926edd8c8",
+ "sha256:cc86c2ef598c15d824a243c2541c29459881c67fc3c0adb6efe2242f8f0ec3af",
+ "sha256:e311dfcb470696e3c077249615893cada598e62fa7c4e4ca090167bd2b7d331f"
+ ],
+ "markers": "platform_system == 'Windows'",
+ "version": "==1.0.4"
},
"txaio": {
"hashes": [
@@ -961,17 +1096,18 @@
},
"typing-extensions": {
"hashes": [
- "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
- "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
+ "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8",
+ "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"
],
"markers": "python_version < '3.10'",
- "version": "==4.9.0"
+ "version": "==4.12.0"
},
"tzdata": {
"hashes": [
"sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
"sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
],
+ "markers": "python_version >= '2'",
"version": "==2024.1"
},
"urllib3": {
@@ -979,7 +1115,7 @@
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_version >= '3.8'",
"version": "==2.2.1"
},
"vine": {
@@ -997,57 +1133,65 @@
],
"version": "==0.2.13"
},
- "zope-interface": {
- "hashes": [
- "sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe",
- "sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac",
- "sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad",
- "sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b",
- "sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000",
- "sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328",
- "sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565",
- "sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f",
- "sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70",
- "sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037",
- "sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b",
- "sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab",
- "sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85",
- "sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099",
- "sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5",
- "sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef",
- "sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c",
- "sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd",
- "sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48",
- "sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd",
- "sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550",
- "sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797",
- "sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe",
- "sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d",
- "sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e",
- "sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1",
- "sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0",
- "sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532",
- "sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f",
- "sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f",
- "sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3",
- "sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a",
- "sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000",
- "sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e",
- "sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce",
- "sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440"
+ "zope.event": {
+ "hashes": [
+ "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26",
+ "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==5.0"
+ },
+ "zope.interface": {
+ "hashes": [
+ "sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854",
+ "sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b",
+ "sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827",
+ "sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4",
+ "sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671",
+ "sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e",
+ "sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c",
+ "sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc",
+ "sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438",
+ "sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8",
+ "sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874",
+ "sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4",
+ "sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82",
+ "sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7",
+ "sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede",
+ "sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341",
+ "sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1",
+ "sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e",
+ "sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2",
+ "sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb",
+ "sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530",
+ "sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934",
+ "sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b",
+ "sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43",
+ "sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7",
+ "sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc",
+ "sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde",
+ "sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79",
+ "sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e",
+ "sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9",
+ "sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15",
+ "sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b",
+ "sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5",
+ "sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250",
+ "sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e",
+ "sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1"
],
"markers": "python_version >= '3.7'",
- "version": "==6.2"
+ "version": "==6.4.post2"
}
},
"develop": {
"asgiref": {
"hashes": [
- "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e",
- "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"
+ "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47",
+ "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"
],
- "markers": "python_version >= '3.7'",
- "version": "==3.7.2"
+ "markers": "python_version >= '3.8'",
+ "version": "==3.8.1"
},
"astroid": {
"hashes": [
@@ -1103,6 +1247,15 @@
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
+ "colorama": {
+ "hashes": [
+ "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
+ "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
+ "version": "==0.4.6"
+ },
"coverage": {
"hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
@@ -1164,11 +1317,12 @@
},
"django": {
"hashes": [
- "sha256:5dd5b787c3ba39637610fe700f54bf158e33560ea0dba600c19921e7ff926ec5",
- "sha256:aaee9fb0fb4ebd4311520887ad2e33313d368846607f82a9a0ed461cd4c35b18"
+ "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777",
+ "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"
],
+ "index": "pypi",
"markers": "python_version >= '3.6'",
- "version": "==3.2.24"
+ "version": "==3.2.25"
},
"django-stubs": {
"hashes": [
@@ -1302,11 +1456,11 @@
},
"platformdirs": {
"hashes": [
- "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068",
- "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"
+ "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee",
+ "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"
],
"markers": "python_version >= '3.8'",
- "version": "==4.2.0"
+ "version": "==4.2.2"
},
"pycodestyle": {
"hashes": [
@@ -1372,16 +1526,17 @@
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
+ "index": "pypi",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==5.4.1"
},
"setuptools": {
"hashes": [
- "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401",
- "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"
+ "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
+ "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
],
"markers": "python_version >= '3.8'",
- "version": "==69.1.0"
+ "version": "==70.0.0"
},
"sqlparse": {
"hashes": [
@@ -1446,11 +1601,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
- "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
+ "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8",
+ "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"
],
"markers": "python_version < '3.10'",
- "version": "==4.9.0"
+ "version": "==4.12.0"
},
"wrapt": {
"hashes": [
diff --git a/manage.py b/manage.py
index a29aa178a..a55c3c5bc 100755
--- a/manage.py
+++ b/manage.py
@@ -3,6 +3,8 @@
import os
import sys
+import sys
+print(sys.version)
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'othello.settings')
diff --git a/othello/apps/auth/migrations/0008_user_is_gauntlet_running.py b/othello/apps/auth/migrations/0008_user_is_gauntlet_running.py
new file mode 100644
index 000000000..25f040084
--- /dev/null
+++ b/othello/apps/auth/migrations/0008_user_is_gauntlet_running.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-25 02:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authentication', '0007_auto_20210422_1152'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='is_gauntlet_running',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/othello/apps/auth/migrations/0009_user_last_gauntlet_run.py b/othello/apps/auth/migrations/0009_user_last_gauntlet_run.py
new file mode 100644
index 000000000..9d6fa98cb
--- /dev/null
+++ b/othello/apps/auth/migrations/0009_user_last_gauntlet_run.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-28 03:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authentication', '0008_user_is_gauntlet_running'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='last_gauntlet_run',
+ field=models.DateTimeField(null=True),
+ ),
+ ]
diff --git a/othello/apps/auth/models.py b/othello/apps/auth/models.py
index 36fbb87f8..a7180fed1 100644
--- a/othello/apps/auth/models.py
+++ b/othello/apps/auth/models.py
@@ -9,6 +9,10 @@ class User(AbstractUser):
is_student = models.BooleanField(default=True, null=False)
is_imported = models.BooleanField(default=False, null=False)
+ #rating
+ is_gauntlet_running = models.BooleanField(default=False, null=False)
+ last_gauntlet_run = models.DateTimeField(null=True)
+
@property
def has_management_permission(self) -> bool:
return self.is_teacher or self.is_staff or self.is_superuser
diff --git a/othello/apps/games/migrations/0031_auto_20240523_1555.py b/othello/apps/games/migrations/0031_auto_20240523_1555.py
new file mode 100644
index 000000000..beed1b59f
--- /dev/null
+++ b/othello/apps/games/migrations/0031_auto_20240523_1555.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.24 on 2024-05-23 19:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0030_auto_20220804_1438'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='submission',
+ name='deviation',
+ field=models.IntegerField(default=350),
+ ),
+ migrations.AddField(
+ model_name='submission',
+ name='rating',
+ field=models.IntegerField(default=400),
+ ),
+ ]
diff --git a/othello/apps/games/migrations/0032_submission_gauntlet.py b/othello/apps/games/migrations/0032_submission_gauntlet.py
new file mode 100644
index 000000000..8bb01e75d
--- /dev/null
+++ b/othello/apps/games/migrations/0032_submission_gauntlet.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-25 02:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0031_auto_20240523_1555'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='submission',
+ name='gauntlet',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/othello/apps/games/migrations/0033_game_is_ranked.py b/othello/apps/games/migrations/0033_game_is_ranked.py
new file mode 100644
index 000000000..35d963d20
--- /dev/null
+++ b/othello/apps/games/migrations/0033_game_is_ranked.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-25 17:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0032_submission_gauntlet'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='game',
+ name='is_ranked',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/othello/apps/games/migrations/0034_auto_20240525_2248.py b/othello/apps/games/migrations/0034_auto_20240525_2248.py
new file mode 100644
index 000000000..11e1a57f1
--- /dev/null
+++ b/othello/apps/games/migrations/0034_auto_20240525_2248.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.25 on 2024-05-26 02:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0033_game_is_ranked'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='submission',
+ name='deviation',
+ ),
+ migrations.AddField(
+ model_name='game',
+ name='ratingDelta',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/othello/apps/games/migrations/0035_auto_20240526_1514.py b/othello/apps/games/migrations/0035_auto_20240526_1514.py
new file mode 100644
index 000000000..5cca157bf
--- /dev/null
+++ b/othello/apps/games/migrations/0035_auto_20240526_1514.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0034_auto_20240525_2248'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='game',
+ name='blackRating',
+ field=models.IntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='game',
+ name='whiteRating',
+ field=models.IntegerField(default=0, null=True),
+ ),
+ ]
diff --git a/othello/apps/games/migrations/0036_game_is_gauntlet.py b/othello/apps/games/migrations/0036_game_is_gauntlet.py
new file mode 100644
index 000000000..14d457224
--- /dev/null
+++ b/othello/apps/games/migrations/0036_game_is_gauntlet.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-28 12:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0035_auto_20240526_1514'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='game',
+ name='is_gauntlet',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/othello/apps/games/models.py b/othello/apps/games/models.py
index d477b9c8a..b1ae8491e 100644
--- a/othello/apps/games/models.py
+++ b/othello/apps/games/models.py
@@ -5,7 +5,7 @@
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField
from django.db import models
-from django.db.models import Q
+from django.db.models import Q, Subquery
from django.utils import timezone
from ...moderator.constants import Player
@@ -28,6 +28,16 @@ def latest(self, **kwargs: Any) -> "models.query.QuerySet[Submission]":
"""
return self.filter(**kwargs).order_by("user", "-created_at").distinct("user")
+ def rated(self, **kwargs: Any) -> "models.query.QuerySet[Submission]":
+ """
+ Returns a set of all the rated submissions for all users
+ """
+ #return self.filter(**kwargs).order_by("user", "-created_at").distinct("user").order_by("-rating")
+ g1 = self.latest() #Submission.objects.filter(user__in=Subquery(self.latest().values('user')))
+ g2 = self.filter(gauntlet=True)
+ g3 = g2.intersection(g1)
+ g3 = g3.order_by("-rating")
+ return g3
class Submission(models.Model):
@@ -38,6 +48,10 @@ class Submission(models.Model):
created_at = models.DateTimeField(auto_now=True)
code = models.FileField(upload_to=_save_path, default=None)
+ #Rating Info
+ rating = models.IntegerField(default=400, null=False)
+ gauntlet = models.BooleanField(default=False, null=False)
+
is_legacy = models.BooleanField(default=False)
tournament_win_year = models.IntegerField(default=-1)
@@ -102,8 +116,15 @@ class Game(models.Model):
forfeit = models.BooleanField(default=False)
outcome = models.CharField(max_length=1, choices=OUTCOME_CHOICES, default="T")
score = models.IntegerField(default=0)
+ ratingDelta = models.IntegerField(default=0)
+
+ #rating values for both sides before rating delta is applied
+ blackRating = models.IntegerField(null=True, default=0)
+ whiteRating = models.IntegerField(null=True, default=0)
is_tournament = models.BooleanField(default=False)
+ is_ranked = models.BooleanField(default=False)
+ is_gauntlet = models.BooleanField(default=False)
playing = models.BooleanField(default=False)
last_heartbeat = models.DateTimeField(default=timezone.now)
diff --git a/othello/apps/games/tasks.py b/othello/apps/games/tasks.py
index 5bd899c0d..fe1a5f8e9 100644
--- a/othello/apps/games/tasks.py
+++ b/othello/apps/games/tasks.py
@@ -26,14 +26,14 @@ def send_through_game_channel(game: Game, event_type: str, object_id: int) -> in
def check_heartbeat(game: Game) -> bool:
- if game.is_tournament:
+ if game.is_tournament or game.is_ranked:
return True
game.refresh_from_db()
return (timezone.now() - game.last_heartbeat).seconds < settings.CLIENT_HEARTBEAT_INTERVAL * 2
def delete_game(game: Game) -> None:
- if not game.is_tournament:
+ if not game.is_tournament and not game.is_ranked:
game.delete()
@@ -80,93 +80,102 @@ def run_game(game_id: int) -> Optional[str]:
send_through_game_channel(game, "game.error", file_deleted.id)
raise RuntimeError("Cannot find a submission code file!")
- error = 0
- with black_runner as player_black, white_runner as player_white:
- last_move = game.moves.create(board=INITIAL_BOARD, player="-", possible=[26, 19, 44, 37])
- send_through_game_channel(game, "game.update", game_id)
- exception = None
-
- while not mod.is_game_over():
- if not check_heartbeat(game) or not game.playing:
- game.playing = False
- game.outcome = "T"
- game.forfeit = False
- game.save(update_fields=["playing", "outcome", "forfeit"])
- return "no ping"
- board, current_player = mod.get_game_state()
-
- try:
- if current_player == Player.BLACK:
- running_turn = player_black.get_move(board, current_player, black_time_limit, last_move)
- elif current_player == Player.WHITE:
- running_turn = player_white.get_move(board, current_player, white_time_limit, last_move)
- except BaseException as e:
- logger.error(f"Error when getting move {game_id}, {current_player}, {str(e)}")
- task_logger.error(str(e))
- exception = e
-
- for log in running_turn:
- print(log)
- game_log = game.logs.create(player=current_player.value, message=log)
- send_through_game_channel(game, "game.log", game_log.id)
- submitted_move, error, extra_time = running_turn.return_value
-
- if exception is not None:
- error = ServerError.UNEXPECTED
-
- if game.runoff:
- if current_player == Player.BLACK:
- black_time_limit = game.time_limit + extra_time
- else:
- white_time_limit = game.time_limit + extra_time
-
- if error != 0:
- game_err = game.errors.create(player=current_player.value, error_code=error.value[0], error_msg=error.value[1])
- if isinstance(error, ServerError):
- game.forfeit = False
- game.outcome = "T"
- elif isinstance(error, UserError):
- game.forfeit = True
- game.outcome = Player.BLACK.value if current_player == Player.WHITE else Player.WHITE.value
- game.playing = False
- game.save(update_fields=["forfeit", "outcome", "playing"])
- send_through_game_channel(game, "game.error", game_err.id)
- break
-
- try:
- if submitted := mod.submit_move(submitted_move):
- possible = submitted
- else:
- game_over = True
- except InvalidMoveError as e:
- game_err = game.errors.create(player=current_player.value, error_code=e.code, error_msg=e.message)
- game.forfeit, game.playing = True, False
- game.outcome = current_player.opposite_player().value
- game.save(update_fields=["forfeit", "outcome", "playing"])
- send_through_game_channel(game, "game.error", game_err.id)
- task_logger.info(f"{game_id}: {current_player.value} submitted invalid move {submitted}")
- break
-
- last_move = game.moves.create(
- player=current_player.value,
- move=submitted_move,
- board=mod.get_board(),
- possible=possible,
- )
- if game_over:
- game.forfeit = False
- game.outcome = mod.outcome()
- game.score = mod.score()
- game.save(update_fields=["forfeit", "score", "outcome", "playing"])
- task_logger.info(f"GAME {game_id} OVER")
- break
+ #print("IM HERE NOW")
+
+ try:
+ error = 0
+ with black_runner as player_black, white_runner as player_white:
+ last_move = game.moves.create(board=INITIAL_BOARD, player="-", possible=[26, 19, 44, 37])
send_through_game_channel(game, "game.update", game_id)
+ exception = None
+
+ while not mod.is_game_over():
+ if not check_heartbeat(game) or not game.playing:
+ game.playing = False
+ game.outcome = "T"
+ game.forfeit = False
+ game.save(update_fields=["playing", "outcome", "forfeit"])
+ return "no ping"
+ board, current_player = mod.get_game_state()
+ #print(board)
+
+ try:
+ if current_player == Player.BLACK:
+ running_turn = player_black.get_move(board, current_player, black_time_limit, last_move)
+ elif current_player == Player.WHITE:
+ running_turn = player_white.get_move(board, current_player, white_time_limit, last_move)
+ except BaseException as e:
+ logger.error(f"Error when getting move {game_id}, {current_player}, {str(e)}")
+ task_logger.error(str(e))
+ exception = e
+
+ for log in running_turn:
+ print(log)
+ game_log = game.logs.create(player=current_player.value, message=log)
+ send_through_game_channel(game, "game.log", game_log.id)
+ submitted_move, error, extra_time = running_turn.return_value
+
+ if exception is not None:
+ error = ServerError.UNEXPECTED
+
+ if game.runoff:
+ if current_player == Player.BLACK:
+ black_time_limit = game.time_limit + extra_time
+ else:
+ white_time_limit = game.time_limit + extra_time
+
+ if error != 0:
+ game_err = game.errors.create(player=current_player.value, error_code=error.value[0], error_msg=error.value[1])
+ if isinstance(error, ServerError):
+ game.forfeit = False
+ game.outcome = "T"
+ elif isinstance(error, UserError):
+ game.forfeit = True
+ game.outcome = Player.BLACK.value if current_player == Player.WHITE else Player.WHITE.value
+ game.playing = False
+ game.save(update_fields=["forfeit", "outcome", "playing"])
+ send_through_game_channel(game, "game.error", game_err.id)
+ break
+
+ try:
+ if submitted := mod.submit_move(submitted_move):
+ possible = submitted
+ else:
+ game_over = True
+ except InvalidMoveError as e:
+ game_err = game.errors.create(player=current_player.value, error_code=e.code, error_msg=e.message)
+ game.forfeit, game.playing = True, False
+ game.outcome = current_player.opposite_player().value
+ game.save(update_fields=["forfeit", "outcome", "playing"])
+ send_through_game_channel(game, "game.error", game_err.id)
+ task_logger.info(f"{game_id}: {current_player.value} submitted invalid move {submitted}")
+ break
+
+ last_move = game.moves.create(
+ player=current_player.value,
+ move=submitted_move,
+ board=mod.get_board(),
+ possible=possible,
+ )
+ if game_over:
+ game.forfeit = False
+ game.outcome = mod.outcome()
+ game.score = mod.score()
+ game.save(update_fields=["forfeit", "score", "outcome", "playing"])
+ task_logger.info(f"GAME {game_id} OVER")
+ break
+ send_through_game_channel(game, "game.update", game_id)
+ except BaseException as error:
+ print(error)
+
game.playing = False
game.save(update_fields=["playing"])
send_through_game_channel(game, "game.update", game_id)
black_runner.stop()
white_runner.stop()
+ # print("DONE", game.playing, game.score)
+
if error != 0 and isinstance(error, ServerError):
if error.value[0] != -8:
raise RuntimeError(f"Game {game_id} encountered a ServerError of value {error.value}")
diff --git a/othello/apps/rating/__init__.py b/othello/apps/rating/__init__.py
new file mode 100644
index 000000000..8e8c3bc82
--- /dev/null
+++ b/othello/apps/rating/__init__.py
@@ -0,0 +1,2 @@
+# things that i messed with
+# import_strategy_sandboxed, __init__.py in sandboxing
\ No newline at end of file
diff --git a/othello/apps/rating/admin.py b/othello/apps/rating/admin.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/othello/apps/rating/apps.py b/othello/apps/rating/apps.py
new file mode 100644
index 000000000..b8bb62532
--- /dev/null
+++ b/othello/apps/rating/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class RatingConfig(AppConfig):
+ name = "othello.apps.rating"
+ label = "rating"
diff --git a/othello/apps/rating/forms.py b/othello/apps/rating/forms.py
new file mode 100644
index 000000000..fd38631c5
--- /dev/null
+++ b/othello/apps/rating/forms.py
@@ -0,0 +1,12 @@
+from django import forms
+
+class MultipleChoiceForm(forms.Form):
+ CHOICES = [
+ ('runbatch', 'Manually run ranked games batch'),
+ ('deletegames', 'Delete all ranked games models'),
+ ('deletegauntlets', 'Delete all gauntlet models'),
+ ('disableauto', 'Disable auto ranked games (also terminates if current)'),
+ ('enableauto', 'Enable auto ranked games'),
+ ]
+
+ choices = forms.ChoiceField(choices=CHOICES, widget=forms.RadioSelect)
diff --git a/othello/apps/rating/migrations/0001_initial.py b/othello/apps/rating/migrations/0001_initial.py
new file mode 100644
index 000000000..c7810e2a1
--- /dev/null
+++ b/othello/apps/rating/migrations/0001_initial.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.2.25 on 2024-05-25 17:01
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import othello.apps.games.validators
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('games', '0032_submission_gauntlet'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Gauntlet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now=True)),
+ ('game_time_limit', models.IntegerField(default=5, validators=[othello.apps.games.validators.validate_game_time_limit])),
+ ('running', models.BooleanField(default=False)),
+ ('finished', models.BooleanField(default=False)),
+ ('terminated', models.BooleanField(default=False)),
+ ('submission', models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='gauntletsubmission', to='games.submission')),
+ ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0002_auto_20240525_1335.py b/othello/apps/rating/migrations/0002_auto_20240525_1335.py
new file mode 100644
index 000000000..8d2fbe5b6
--- /dev/null
+++ b/othello/apps/rating/migrations/0002_auto_20240525_1335.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.2.25 on 2024-05-25 17:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0032_submission_gauntlet'),
+ ('rating', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='gauntlet',
+ name='game1',
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='g1', to='games.game'),
+ ),
+ migrations.AddField(
+ model_name='gauntlet',
+ name='game2',
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='g2', to='games.game'),
+ ),
+ migrations.AddField(
+ model_name='gauntlet',
+ name='game3',
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='g3', to='games.game'),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0003_auto_20240525_2011.py b/othello/apps/rating/migrations/0003_auto_20240525_2011.py
new file mode 100644
index 000000000..d09b1865d
--- /dev/null
+++ b/othello/apps/rating/migrations/0003_auto_20240525_2011.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.25 on 2024-05-26 00:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0002_auto_20240525_1335'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AddField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AddField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0004_alter_gauntlet_myside1.py b/othello/apps/rating/migrations/0004_alter_gauntlet_myside1.py
new file mode 100644
index 000000000..297b86a6d
--- /dev/null
+++ b/othello/apps/rating/migrations/0004_alter_gauntlet_myside1.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-26 02:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0003_auto_20240525_2011'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0005_auto_20240526_1510.py b/othello/apps/rating/migrations/0005_auto_20240526_1510.py
new file mode 100644
index 000000000..fe7f98d70
--- /dev/null
+++ b/othello/apps/rating/migrations/0005_auto_20240526_1510.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0004_alter_gauntlet_myside1'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0006_alter_gauntlet_myside2.py b/othello/apps/rating/migrations/0006_alter_gauntlet_myside2.py
new file mode 100644
index 000000000..7a0b86bf6
--- /dev/null
+++ b/othello/apps/rating/migrations/0006_alter_gauntlet_myside2.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0005_auto_20240526_1510'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0007_auto_20240526_1512.py b/othello/apps/rating/migrations/0007_auto_20240526_1512.py
new file mode 100644
index 000000000..c3854e1af
--- /dev/null
+++ b/othello/apps/rating/migrations/0007_auto_20240526_1512.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0006_alter_gauntlet_myside2'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0008_auto_20240526_1514.py b/othello/apps/rating/migrations/0008_auto_20240526_1514.py
new file mode 100644
index 000000000..0e748d339
--- /dev/null
+++ b/othello/apps/rating/migrations/0008_auto_20240526_1514.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0007_auto_20240526_1512'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0009_auto_20240526_1536.py b/othello/apps/rating/migrations/0009_auto_20240526_1536.py
new file mode 100644
index 000000000..818576798
--- /dev/null
+++ b/othello/apps/rating/migrations/0009_auto_20240526_1536.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.25 on 2024-05-26 19:36
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0008_auto_20240526_1514'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RankedManager',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('auto_run', models.BooleanField(default=False)),
+ ('next_auto_run', models.DateTimeField(auto_now=True)),
+ ('running', models.BooleanField(default=False)),
+ ('celery_task_id', models.CharField(default='', max_length=48)),
+ ],
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0010_alter_gauntlet_myside3.py b/othello/apps/rating/migrations/0010_alter_gauntlet_myside3.py
new file mode 100644
index 000000000..45ff0e1b5
--- /dev/null
+++ b/othello/apps/rating/migrations/0010_alter_gauntlet_myside3.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-26 23:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0009_auto_20240526_1536'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0011_auto_20240527_2119.py b/othello/apps/rating/migrations/0011_auto_20240527_2119.py
new file mode 100644
index 000000000..7f318387a
--- /dev/null
+++ b/othello/apps/rating/migrations/0011_auto_20240527_2119.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.25 on 2024-05-28 01:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0010_alter_gauntlet_myside3'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='rankedmanager',
+ name='next_auto_run',
+ field=models.DateTimeField(),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0012_gauntlet_pastrating.py b/othello/apps/rating/migrations/0012_gauntlet_pastrating.py
new file mode 100644
index 000000000..1ce48cf6f
--- /dev/null
+++ b/othello/apps/rating/migrations/0012_gauntlet_pastrating.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-28 02:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0011_auto_20240527_2119'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='gauntlet',
+ name='pastRating',
+ field=models.IntegerField(default=400),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0013_gauntlet_celery_task_id.py b/othello/apps/rating/migrations/0013_gauntlet_celery_task_id.py
new file mode 100644
index 000000000..e1273efdb
--- /dev/null
+++ b/othello/apps/rating/migrations/0013_gauntlet_celery_task_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-28 02:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0012_gauntlet_pastrating'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='gauntlet',
+ name='celery_task_id',
+ field=models.CharField(default='', max_length=48),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0014_alter_gauntlet_myside2.py b/othello/apps/rating/migrations/0014_alter_gauntlet_myside2.py
new file mode 100644
index 000000000..b6cd79fc4
--- /dev/null
+++ b/othello/apps/rating/migrations/0014_alter_gauntlet_myside2.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-05-28 03:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0013_gauntlet_celery_task_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0015_auto_20240528_0833.py b/othello/apps/rating/migrations/0015_auto_20240528_0833.py
new file mode 100644
index 000000000..cfdb16505
--- /dev/null
+++ b/othello/apps/rating/migrations/0015_auto_20240528_0833.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-05-28 12:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0014_alter_gauntlet_myside2'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/__init__.py b/othello/apps/rating/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/othello/apps/rating/models.py b/othello/apps/rating/models.py
new file mode 100644
index 000000000..8a6127e3d
--- /dev/null
+++ b/othello/apps/rating/models.py
@@ -0,0 +1,76 @@
+from typing import Any
+
+from django.db import models
+from django.utils.timezone import now
+
+from ..games.models import Game, Submission
+from ..games.validators import validate_game_time_limit
+from ..auth.models import User
+#from .validators import validate_tournament_rounds
+
+from django.contrib.auth import get_user_model
+
+import random
+
+class Gauntlet(models.Model):
+ # objects: Any = TournamentSet().as_manager()
+ user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, null=True)
+
+ created_at = models.DateTimeField(auto_now=True)
+ game_time_limit = models.IntegerField(default=5, validators=[validate_game_time_limit])
+
+ running = models.BooleanField(default=False)
+ finished = models.BooleanField(default=False)
+ terminated = models.BooleanField(default=False)
+
+ celery_task_id = models.CharField(max_length=48, default="")
+
+ #asdf = models.ForeignKey(Submission, on_delete=models.PROTECT, null = False)
+ submission = models.ForeignKey(
+ Submission,
+ blank=False,
+ null=False,
+ on_delete=models.PROTECT,
+ related_name="gauntletsubmission",
+ default=None,
+ )
+ pastRating = models.IntegerField(default=400)
+
+ # celery_task_id = models.CharField(max_length=48, default="")
+ game1 = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="g1", null=False, blank=False, default=None)
+ game2 = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="g2", null=False, blank=False, default=None)
+ game3 = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="g3", null=False, blank=False, default=None)
+
+ mySide1 = models.CharField(default=random.choice(['x', 'o']), max_length=1)
+ mySide2 = models.CharField(default=random.choice(['x', 'o']), max_length=1)
+ mySide3 = models.CharField(default=random.choice(['x', 'o']), max_length=1)
+
+ def __str__(self) -> str:
+ return "gauntlet object"
+
+ def __repr__(self) -> str:
+ return f"gauntlet object {self.user} {self.created_at}"
+
+class RankedManager(models.Model):
+ auto_run = models.BooleanField(default=False, null=False)
+ next_auto_run = models.DateTimeField()
+ running = models.BooleanField(default=False, null=False)
+ celery_task_id = models.CharField(max_length=48, default="")
+
+ def __str__(self) -> str:
+ return f"Auto Run {self.auto_run}, Next Auto {self.next_auto_run}, Running {self.running}"
+
+ def __repr__(self) -> str:
+ return f"Auto Run {self.auto_run}, Next Auto {self.next_auto_run}, Running {self.running}"
+
+
+
+
+# class RankedGame(models.Model):
+
+# batch = models.DateField()
+
+# game = models.ForeignKey(Game, on_delete=models.CASCADE, null=False, blank=False)
+
+# def __str__(self) -> str:
+# return f"Ranked game @ {str(self.batch)} - {self.game.black.get_user_name()} v. {self.game.white.get_user_name()}"
\ No newline at end of file
diff --git a/othello/apps/rating/tasks.py b/othello/apps/rating/tasks.py
new file mode 100644
index 000000000..a9d40626a
--- /dev/null
+++ b/othello/apps/rating/tasks.py
@@ -0,0 +1,292 @@
+import logging, random, time, sys
+from datetime import datetime, timedelta
+from typing import Optional
+
+from asgiref.sync import async_to_sync
+from celery import shared_task
+from celery.utils.log import get_task_logger
+from channels.layers import get_channel_layer
+
+from django.conf import settings
+
+from ..games.models import Submission, Game
+from ..auth.models import User
+from .models import Gauntlet, RankedManager
+from ..games.tasks import run_game
+
+from typing import Iterator, List, Tuple, TypeVar
+T = TypeVar("T")
+
+logger = logging.getLogger("othello")
+task_logger = get_task_logger(__name__)
+
+
+def chunks(v: List[T], n: int) -> Iterator[Tuple[T]]:
+ for i in range(0, len(v), n):
+ yield tuple(v[i: i + n])
+
+@shared_task
+def runGauntlet(user_id: int) -> str:
+ user = User.objects.filter(id=user_id).first()
+ myGauntlet = Gauntlet.objects.filter(user=user, finished=False).first()
+
+ #with transaction.atomic():
+ myGauntlet.running = True # this is redundancy
+ myGauntlet.save()
+
+ myGauntlet.game1.playing = True
+ myGauntlet.game2.playing = True
+ myGauntlet.game3.playing = True
+ myGauntlet.game3.save()
+ myGauntlet.game2.save()
+ myGauntlet.game1.save()
+
+ run_game(myGauntlet.game1.id)
+ # myGauntlet.game1.refresh_from_db()
+ # print("game 1 is finished")
+ # print(myGauntlet.game1.outcome, myGauntlet.game1.playing, myGauntlet.game1.score)
+
+ run_game(myGauntlet.game2.id)
+ # myGauntlet.game2.refresh_from_db()
+ # print("game 2 is finished")
+ # print(myGauntlet.game2.outcome, myGauntlet.game2.playing, myGauntlet.game2.score)
+
+ run_game(myGauntlet.game3.id)
+ # myGauntlet.game3.refresh_from_db()
+ # print("game 3 is finished")
+ # print(myGauntlet.game3.outcome, myGauntlet.game3.playing, myGauntlet.game3.score)
+
+ user.is_gauntlet_running = False
+ user.save() #update_fields=["is_gauntlet_running"]
+
+ myGauntlet.finished = True
+ myGauntlet.save()
+
+ myGauntlet.refresh_from_db()
+ if myGauntlet.game1.outcome == myGauntlet.mySide1 and myGauntlet.game2.outcome == myGauntlet.mySide2 and myGauntlet.game3.outcome == myGauntlet.mySide3:
+ myGauntlet.submission.gauntlet = True
+ myGauntlet.submission.rating = myGauntlet.pastRating
+ myGauntlet.submission.save()
+
+ allGauntlets = Gauntlet.objects.filter(finished=False).order_by("-created_at")
+ if allGauntlets.first():
+ allGauntlets.first().running = True
+ allGauntlets.first().save()
+ runGauntlet.delay(allGauntlets.first().user.id)
+
+def calculateElo(r1, r2, score):
+ # Let K = 32
+ # WHERE r1 >= r2
+ assert r1 >= r2
+ p = (1 / (1 + 10 ** ((r1-r2) / 400)))
+ expected = 64 * (1-p) - 32
+
+ score = score*p + (32 if score > 0 else -32 if score < 0 else 0)
+
+ delta = (32 / 64) * (score - expected)
+ return round(delta)
+
+def doPairing(n):
+ assert n >= 2
+ if n == 3:
+ psbl = [
+ [(0, 1), (1, 2), (0, 2)],
+ [(1, 0), (1, 2), (0, 2)],
+ [(0, 1), (2, 1), (0, 2)],
+ [(1, 0), (2, 1), (0, 2)],
+ [(0, 1), (1, 2), (2, 0)],
+ [(1, 0), (1, 2), (2, 0)],
+ [(0, 1), (2, 1), (2, 0)],
+ [(1, 0), (2, 1), (2, 0)],
+ ]
+ return random.choice(psbl)
+ elif n == 2:
+ psbl = [
+ [(0, 1), (1, 0)],
+ [(1, 0), (0, 1)],
+ ]
+ return random.choice(psbl)
+
+ pairs = []
+ lst = [i for i in range(n)]
+
+ maxDelta = max(2, min(n//10, 8)) #isn't a hard max, but is conducive
+
+ while lst:
+ l = lst[0]
+ d = random.randint(1, min(maxDelta, len(lst)-1))
+ pairs.append((l, lst[d]))
+ lst.remove(lst[d])
+ lst.remove(l)
+
+ if len(lst) < 2: break
+
+ r = lst[-1]
+ d = random.randint(1, min(maxDelta, len(lst)-1))
+ pairs.append((r, lst[-1-d]))
+ lst.remove(lst[-1-d])
+ lst.remove(r)
+
+ if len(lst) < 2: break
+
+ lst2 = [i for i in range(n)]
+ if len(lst) == 1:
+ d1 = random.randint(lst[0]+1, min(lst[0]+maxDelta, n-1))
+ d2 = random.randint(max(0, lst[0]-maxDelta), lst[0]-1)
+ pairs.append((lst[0], d1))
+ pairs.append((lst[0], d2))
+ lst2.remove(d1)
+ lst2.remove(d2)
+ lst2.remove(lst[0])
+
+ lst = lst2
+ while lst:
+ l = lst[0]
+ d = random.randint(1, min(maxDelta, len(lst)-1))
+ pairs.append((l, lst[d]))
+ lst.remove(lst[d])
+ lst.remove(l)
+
+ if len(lst) < 2: break
+
+ r = lst[-1]
+ d = random.randint(1, min(maxDelta, len(lst)-1))
+ pairs.append((r, lst[-1-d]))
+ lst.remove(lst[-1-d])
+ lst.remove(r)
+
+ if len(lst) < 2: break
+
+ # print(pairs)
+ # print(len(pairs))
+
+ assert len(pairs) == n
+ occur = [0 for _ in range(n)]
+ for i, j in pairs:
+ occur[i] += 1
+ occur[j] += 1
+ for i in range(n):
+ assert occur[i] == 2
+
+ return pairs
+
+
+def deleteAllRankedGames():
+ games = Game.objects.filter(is_ranked=True)
+ games.delete()
+ #games.save()
+
+def getNextScrimTime():
+ #batches happen monday, wednesday, friday, 4pm
+ today = datetime.today()
+ mon = (-today.weekday() + 7) % 7
+ wed = (2-today.weekday()+7) % 7
+ fri = (4-today.weekday()+7) % 7
+
+ if mon == 0 or wed == 0 or fri == 0:
+ if datetime.now().hour >= 16: # if its after 4pm, then we dont want to run today
+ if mon == 0: mon = 1000
+ if wed == 0: wed = 1000
+ if fri == 0: fri = 1000
+
+ if mon < wed and mon < fri:
+ logger.warning("Next run is monday")
+ today += timedelta(days=mon)
+ elif wed < mon and wed < fri:
+ logger.warning("Next run is wednesday")
+ today += timedelta(days=wed)
+ elif fri < mon and fri < wed:
+ logger.warning("Next run is friday")
+ today += timedelta(days=fri)
+
+ today = today.replace(hour=16, minute=0, second=0, microsecond=0)
+
+ return today
+
+@shared_task
+def runAllScrims():
+ manager = RankedManager.objects.first()
+ if manager.running:
+ logger.warning("Did not run a batch since one already is running")
+ return
+
+ manager.running = True
+ manager.save()
+
+ deleteAllRankedGames()
+
+ players = Submission.objects.rated()
+ matches = doPairing(players.count())
+ submissions = list(players)
+
+ # print(matches, submissions)
+
+ for round_matches in chunks(matches, 1 if sys.platform == "win32" else settings.CONCURRENT_GAME_LIMIT):
+ games = []
+
+ for i, j in round_matches:
+ submissions[i].refresh_from_db()
+ submissions[j].refresh_from_db()
+ game = Game.objects.create(
+ black=submissions[i],
+ white=submissions[j],
+ blackRating=submissions[i].rating,
+ whiteRating=submissions[j].rating,
+ time_limit=5,
+ playing=True,
+ is_ranked=True
+ )
+ games.append(game)
+ logger.warning(f"Upcoming matches: {str(game)}")
+ run_game.delay(game.id)
+
+ # tasks = {game: run_game.delay(game.id) for game in games}
+ # return
+
+ for it in range(1000): # if this doesn't finish in 15 minutes, just moves on
+ running = False
+ for game in games:
+ #tasks = {game: run_tournament_game.delay(game.id) for game in games}
+ game.refresh_from_db()
+ running = running or game.playing
+ # print("check ", running)
+ if not running: break
+ time.sleep(1)
+
+ running = False
+ for game in games:
+ running = running or game.playing
+ if running:
+ logger.warn("Some games didn't finish. Very bad.")
+
+ for game in games:
+ r1 = game.black.rating
+ r2 = game.white.rating
+ if r1 >= r2:
+ delta = calculateElo(r1, r2, game.score)
+ game.ratingDelta = delta
+ r1 += delta
+ r2 -= delta
+ else:
+ delta = calculateElo(r2, r1, -game.score)
+ game.ratingDelta = -delta
+ r1 -= delta
+ r2 += delta
+ game.black.rating = r1
+ game.white.rating = r2
+ game.black.save()
+ game.white.save()
+
+ game.save()
+
+ manager.running = False
+
+ # THIS IS NOT TESTED - has the precondition that this is the only task queued, so its safe to queue another runAllScrims
+ if manager.auto_run:
+ logger.warning("queueing batch for later")
+ manager.next_auto_run = getNextScrimTime()
+ task = runAllScrims.apply_async([], eta=manager.next_auto_run)
+ manager.celery_task_id = task.id
+
+ manager.save()
+ return
\ No newline at end of file
diff --git a/othello/apps/rating/templatetags/__init__.py b/othello/apps/rating/templatetags/__init__.py
new file mode 100644
index 000000000..a2953361e
--- /dev/null
+++ b/othello/apps/rating/templatetags/__init__.py
@@ -0,0 +1 @@
+#todo, make ranked games seperate from gauntlet games
\ No newline at end of file
diff --git a/othello/apps/rating/templatetags/extras.py b/othello/apps/rating/templatetags/extras.py
new file mode 100644
index 000000000..43cc8f546
--- /dev/null
+++ b/othello/apps/rating/templatetags/extras.py
@@ -0,0 +1,28 @@
+from django import template
+
+register = template.Library()
+
+@register.filter
+def parseBlack(game):
+ if game.ratingDelta >= 0:
+ return f"{game.blackRating} + {abs(game.ratingDelta)}"
+ elif game.ratingDelta < 0:
+ return f"{game.blackRating} - {abs(game.ratingDelta)}"
+ return "ERROR"
+
+@register.filter
+def parseWhite(game):
+ if game.ratingDelta > 0:
+ return f"{game.whiteRating} - {abs(game.ratingDelta)}"
+ elif game.ratingDelta <= 0:
+ return f"{game.whiteRating} + {abs(game.ratingDelta)}"
+ return "ERROR"
+
+@register.filter
+def parseScore(game):
+ # a - b = score
+ # a + b = 64
+ # 2a = 64 - score
+ # 2b = 64 + score
+ score = game.score
+ return f"{int(32-score/2)}-{int(32+score/2)}"
\ No newline at end of file
diff --git a/othello/apps/rating/urls.py b/othello/apps/rating/urls.py
new file mode 100644
index 000000000..6969e6abc
--- /dev/null
+++ b/othello/apps/rating/urls.py
@@ -0,0 +1,14 @@
+from django.urls import path
+
+from . import views
+
+app_name = "rating"
+
+urlpatterns = [
+ path("gauntlet", views.gauntlet, name="gauntlet"),
+ path("help", views.help, name="help"),
+ path("standings", views.standings, name="standings"),
+ path("history", views.history, name="history"),
+ path("manage", views.manage, name="manage"),
+ path("deleteGauntlet", views.deleteGauntlet, name="deleteGauntlet")
+]
diff --git a/othello/apps/rating/views.py b/othello/apps/rating/views.py
new file mode 100644
index 000000000..00bd930b8
--- /dev/null
+++ b/othello/apps/rating/views.py
@@ -0,0 +1,241 @@
+import json, sys
+import logging
+from datetime import timedelta, datetime
+import pytz
+
+from django.contrib.auth.decorators import login_required
+from ..auth.decorators import management_only
+from django.http import FileResponse, HttpRequest, HttpResponse
+from django.shortcuts import get_object_or_404, redirect, render
+from django.utils import timezone
+from django.core.paginator import Paginator
+
+from celery.result import AsyncResult
+
+from ..games.models import Submission, Game
+from .tasks import runGauntlet, runAllScrims, deleteAllRankedGames, getNextScrimTime
+from .models import Gauntlet, RankedManager
+from .forms import MultipleChoiceForm
+
+logger = logging.getLogger("othello")
+
+def formatGameInfo(side, game):
+ if not game or not side: return ""
+ if game.playing:
+ return "running"
+ payload = f"Won" if game.outcome == side else "Fail (tie or loss)"
+ return f"{payload}, Score: {abs(game.score) if game.outcome == side else -abs(game.score)}"
+
+@login_required
+def gauntlet(request: HttpRequest) -> HttpResponse:
+ #print(Gauntlet.objects.all())
+ # request.user.is_gauntlet_running = False
+ # request.user.save()
+ # Gauntlet.objects.all().delete()
+ # request.user.is_superuser = True
+ # #request.user.is_
+ # request.user.save()
+ # print(request.user.last_gauntlet_run, datetime.now(pytz.timezone('EST')))
+ # print((datetime.now(pytz.timezone('EST')) - request.user.last_gauntlet_run).total_seconds())
+
+ gauntlets = Gauntlet.objects.all().filter(user=request.user)
+ if request.method == "GET":
+ if not request.user.is_gauntlet_running: # we don't have a gauntlet submitted, just render the default
+ choice = Submission.objects.filter(user=request.user).order_by("-created_at").first().get_submission_name()
+ return render(
+ request,
+ "rating/gauntlet.html",
+ {
+ "recent_submission": f"Submission: {str(choice)}",
+ "last_gauntlet": gauntlets.first() if gauntlets else None,
+ "g1": formatGameInfo(gauntlets.first().mySide1 if gauntlets else None, gauntlets.first().game1 if gauntlets else None),
+ "g2": formatGameInfo(gauntlets.first().mySide2 if gauntlets else None, gauntlets.first().game2 if gauntlets else None),
+ "g3": formatGameInfo(gauntlets.first().mySide3 if gauntlets else None, gauntlets.first().game3 if gauntlets else None),
+ },
+ )
+ else: # we have a gauntlet submitted
+ myGauntlet = gauntlets.filter(finished=False, user=request.user).order_by("created_at")
+ if myGauntlet:
+ myGauntlet = myGauntlet.first()
+ else:
+ return redirect("/rating/deleteGauntlet") #something weird happened, delete everything
+
+ inQueue = True # lets check if the gauntlet is running, or just in queue
+ if myGauntlet.running:
+ inQueue = False
+
+ return render(request, "rating/gauntletrunning.html", {
+ "queue": inQueue,
+ "g1": formatGameInfo(myGauntlet.mySide1, myGauntlet.game1),
+ "g2": formatGameInfo(myGauntlet.mySide2, myGauntlet.game2),
+ "g3": formatGameInfo(myGauntlet.mySide3, myGauntlet.game3),
+ })
+ else: # post request to submit a gauntlet
+ # see if we have already have an unfinished gauntlet active
+ if request.user.last_gauntlet_run and (datetime.now(pytz.timezone('EST')) - request.user.last_gauntlet_run).total_seconds() < 60:
+ return render(request, "rating/throttle.html")
+ request.user.is_gauntlet_running = True
+ request.user.last_gauntlet_run = datetime.now(pytz.timezone('EST'))
+ request.user.save()
+
+ myGauntlet = gauntlets.filter(finished=False).order_by("created_at")
+ if myGauntlet: # we have already submitted a gauntlet, stop here
+ return redirect("/rating/gauntlet")
+ else: # we create a new gauntlet
+ pastGauntlet = Gauntlet.objects.filter(user=request.user)
+ pastRating = 400
+ if pastGauntlet.count() > 0:
+ pastGauntlet = pastGauntlet.first()
+ pastRating = max(400, pastGauntlet.submission.rating)
+ pastGauntlet.game1.delete()
+ pastGauntlet.game2.delete()
+ pastGauntlet.game3.delete()
+ pastGauntlet.delete()
+ Gauntlet.objects.filter(user=request.user).delete()
+ # a lot of delete redudancy
+
+ # keep one gauntlet run saved. since we're making a new one, delete the old one
+ submission = Submission.objects.filter(user=request.user).order_by("-created_at").first()
+ game1 = Game.objects.create(
+ black=submission,
+ white=submission,
+ time_limit=5,
+ playing=False,
+ last_heartbeat=timezone.now(),
+ runoff=False,
+ is_gauntlet=True,
+ )
+ game2 = Game.objects.create(
+ black=submission,
+ white=submission,
+ time_limit=5,
+ playing=False,
+ last_heartbeat=timezone.now(),
+ runoff=False,
+ is_gauntlet=True,
+ )
+ game3 = Game.objects.create(
+ black=submission,
+ white=submission,
+ time_limit=5,
+ playing=False,
+ last_heartbeat=timezone.now(),
+ runoff=False,
+ is_gauntlet=True,
+ )
+ myGauntlet = Gauntlet.objects.create(
+ user = request.user,
+ submission = submission,
+ game1 = game1,
+ game2 = game2,
+ game3 = game3,
+ pastRating = pastRating
+ )
+ if myGauntlet.mySide1 == 'x':
+ myGauntlet.game1.black = submission
+ else:
+ myGauntlet.game1.white = submission
+ if myGauntlet.mySide2 == 'x':
+ myGauntlet.game2.black = submission
+ else:
+ myGauntlet.game2.white = submission
+ if myGauntlet.mySide3 == 'x':
+ myGauntlet.game3.black = submission
+ else:
+ myGauntlet.game3.white = submission
+ myGauntlet.save()
+
+ gauntlets = Gauntlet.objects.all().filter(finished=False).order_by("created_at")
+ if myGauntlet == gauntlets.first() and not myGauntlet.running: # check if this gauntlet is the first one submitted, then we need to start running
+ myGauntlet.running = True
+ myGauntlet.save()
+ #runGauntlet(request.user.id) #.delay(request.user.id)
+ runGauntlet.delay(request.user.id)
+ return redirect("/rating/gauntlet")
+
+ return redirect("/rating/gauntlet") # its just in queue
+
+@login_required
+def deleteGauntlet(request: HttpRequest) -> HttpResponse:
+ request.user.is_gauntlet_running = False
+ request.user.save()
+ gauntlets = Gauntlet.objects.all().filter(user=request.user).order_by("-created_at")
+ if gauntlets:
+ AsyncResult(gauntlets.first().celery_task_id).revoke(terminate=True)
+ gauntlets.first().game1.delete()
+ gauntlets.first().game2.delete()
+ gauntlets.first().game3.delete()
+ gauntlets.delete()
+
+ return redirect("/rating/gauntlet")
+
+def history(request: HttpRequest) -> HttpResponse:
+ if request.method == "GET":
+ games = list(Game.objects.filter(is_ranked=True).order_by("-created_at"))
+
+ return render(
+ request,
+ "rating/history.html",
+ {"games": games},
+ )
+
+ return redirect("/")
+
+def help(request: HttpRequest) -> HttpResponse:
+ return render(request, "rating/help.html")
+
+def standings(request: HttpRequest) -> HttpResponse:
+ # Game.objects.all().delete()
+ players = Submission.objects.rated()
+
+ page_obj = Paginator(players, 10).get_page(request.GET.get("page", "1"))
+ offset = 10 * (page_obj.number - 1)
+
+ return render(
+ request,
+ "rating/standings.html",
+ {"next_time": RankedManager.objects.first().next_auto_run, "players": players, "page_obj": page_obj, "offset": offset},
+ )
+
+@management_only
+def manage(request: HttpRequest) -> HttpResponse:
+ manager = RankedManager.objects.first()
+
+ if request.method == 'POST':
+ form = MultipleChoiceForm(request.POST)
+ if form.is_valid():
+ selected_choice = form.cleaned_data['choices']
+ #print(selected_choice)
+ if selected_choice == 'runbatch':
+ AsyncResult(manager.celery_task_id).revoke(terminate=True)
+ if sys.platform == 'win32':
+ runAllScrims()
+ else:
+ runAllScrims.delay()
+ elif selected_choice == 'deletegames':
+ deleteAllRankedGames()
+ elif selected_choice == 'deletegauntlets':
+ Gauntlet.objects.all().delete()
+ elif selected_choice == 'disableauto':
+ manager.auto_run = False
+ AsyncResult(manager.celery_task_id).revoke(terminate=True)
+ manager.save()
+
+ # print(AsyncResult(manager.celery_task_id).failed())
+ # print(AsyncResult(manager.celery_task_id).state)
+
+ manager.save()
+ elif selected_choice == 'enableauto':
+ manager.auto_run = True
+ manager.next_auto_run = getNextScrimTime()
+
+ if AsyncResult(manager.celery_task_id).state != "PENDING":
+ logger.warning("queueing batch for later")
+ task = runAllScrims.apply_async([], eta=manager.next_auto_run)
+ manager.celery_task_id = task.id
+ manager.save()
+
+
+ else:
+ form = MultipleChoiceForm()
+ return render(request, 'rating/manage.html', {'form': form, "manager": manager})
diff --git a/othello/moderator/runners.py b/othello/moderator/runners.py
index 66187b7a4..6bddf61c5 100644
--- a/othello/moderator/runners.py
+++ b/othello/moderator/runners.py
@@ -3,6 +3,7 @@
import signal
import subprocess
import time
+import sys
from typing import Generator, Tuple, Union
import psutil
@@ -44,7 +45,10 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
def start(self):
- cmd_args = ["python3", "-u", self.driver, self.path]
+ if sys.platform == "win32":
+ cmd_args = ["python", "-u", self.driver, self.path]
+ else:
+ cmd_args = ["python3", "-u", self.driver, self.path]
if not settings.DEBUG:
cmd_args = get_sandbox_args(
cmd_args,
@@ -54,26 +58,39 @@ def start(self):
], # WARNING: Making the submission directory writable creates potential for extremely dangerous symlink attacks
)
- self.process = subprocess.Popen(
- cmd_args,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- bufsize=0,
- cwd=os.path.dirname(self.path),
- preexec_fn=os.setpgrp,
- )
+ if sys.platform == "win32": #windows does not have os.setpgrp
+ self.process = subprocess.Popen(
+ cmd_args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=0,
+ cwd=os.path.dirname(self.path),
+ # preexec_fn=os.setpgrp,
+ )
+ else:
+ self.process = subprocess.Popen(
+ cmd_args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=0,
+ cwd=os.path.dirname(self.path),
+ preexec_fn=os.setpgrp,
+ )
def stop(self):
if self.process is not None:
try:
children = psutil.Process(self.process.pid).children(recursive=True)
- os.killpg(self.process.pid, signal.SIGKILL)
+ if sys.platform != "win32":
+ os.killpg(self.process.pid, signal.SIGKILL)
except psutil.NoSuchProcess:
self.process = None
return
except ProcessLookupError:
self.process.kill()
+
for child in children:
try:
child.kill()
@@ -85,6 +102,7 @@ def stop(self):
def get_move(
self, board: str, player: Player, time_limit: int, last_move: Move
) -> Generator[str, None, Union[Tuple[int, int, int], Tuple[int, ServerError, int], Tuple[int, UserError, int]], ]:
+
if self.process.poll():
print(self.process.communicate())
return -1, ServerError.PROCESS_EXITED, -1
@@ -99,31 +117,71 @@ def get_move(
self.process.stdin.flush()
move, extra_time = -1, 0
- start, total_timeout = time.time(), time_limit + 10
- while move == -1:
- if self.process.poll():
- print(self.process.communicate())
- return -1, ServerError.PROCESS_EXITED, -1
- if (timeout := total_timeout - (time.time() - start)) <= 0:
- return -1, ServerError.TIMEOUT, -1
-
- files_ready = select.select([self.process.stdout, self.process.stderr], [], [], timeout)[0]
- if self.process.stderr in files_ready:
- yield self.process.stderr.read(8192).decode("latin-1")
- if self.process.stdout in files_ready:
- try:
- parts = self.process.stdout.readline().decode("latin-1").split(";")
- move, extra_time = int(parts[0]), int(parts[1])
- print(f"GOT MOVE {player} {move};{extra_time}")
-
- if self.is_legacy:
- if move not in MOVES_10x10:
- return -1, UserError.READ_INVALID, -1
- else:
- if move < 0 or move >= 64:
- return -1, UserError.READ_INVALID, -1
- except (ValueError, IndexError):
+ if sys.platform == "win32":
+ #behold, chatgpt
+ def possible_moves(board_str, player):
+ def is_on_board(x, y):
+ return 0 <= x < 8 and 0 <= y < 8
+
+ def opponent(player):
+ return 'o' if player == 'x' else 'x'
+
+ # Convert the single string into a 2D list (8x8 board)
+ board = [list(board_str[i:i + 8]) for i in range(0, 64, 8)]
+ directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]
+ possible_moves = []
+
+ for x in range(8):
+ for y in range(8):
+ if board[x][y] == '.':
+ for dx, dy in directions:
+ nx, ny = x + dx, y + dy
+ if is_on_board(nx, ny) and board[nx][ny] == opponent(player):
+ while is_on_board(nx, ny) and board[nx][ny] == opponent(player):
+ nx += dx
+ ny += dy
+ if is_on_board(nx, ny) and board[nx][ny] == player:
+ possible_moves.append(y+x*8)
+ break
+
+ return possible_moves
+
+ import random
+ move = random.choice(possible_moves(board, player))
+
+ if self.is_legacy:
+ if move not in MOVES_10x10:
+ return -1, UserError.READ_INVALID, -1
+ else:
+ if move < 0 or move >= 64:
return -1, UserError.READ_INVALID, -1
+ else:
+ start, total_timeout = time.time(), time_limit + 10
+ while move == -1:
+ if self.process.poll():
+ print(self.process.communicate())
+ return -1, ServerError.PROCESS_EXITED, -1
+ if (timeout := total_timeout - (time.time() - start)) <= 0:
+ return -1, ServerError.TIMEOUT, -1
+
+ files_ready = select.select([self.process.stdout, self.process.stderr], [], [], timeout)[0]
+ if self.process.stderr in files_ready:
+ yield self.process.stderr.read(8192).decode("latin-1")
+ if self.process.stdout in files_ready:
+ try:
+ parts = self.process.stdout.readline().decode("latin-1").split(";")
+ move, extra_time = int(parts[0]), int(parts[1])
+ print(f"GOT MOVE {player} {move};{extra_time}")
+
+ if self.is_legacy:
+ if move not in MOVES_10x10:
+ return -1, UserError.READ_INVALID, -1
+ else:
+ if move < 0 or move >= 64:
+ return -1, UserError.READ_INVALID, -1
+ except (ValueError, IndexError):
+ return -1, UserError.READ_INVALID, -1
+
return (move, 0, extra_time) if not self.is_legacy else (MOVES_10x10[move], 0, extra_time)
diff --git a/othello/sandboxing/__init__.py b/othello/sandboxing/__init__.py
index 0d2b2d56b..fba4ea05a 100644
--- a/othello/sandboxing/__init__.py
+++ b/othello/sandboxing/__init__.py
@@ -1,6 +1,6 @@
import json
import logging
-import os
+import os, sys
import subprocess
import traceback
from typing import Dict, List, Optional
@@ -11,6 +11,9 @@
def import_strategy_sandboxed(path: str) -> Optional[Dict[str, str]]:
+ if sys.platform == "win32":
+ return None
+
cmd_args = ["python3", "-u", settings.IMPORT_DRIVER, path]
if not settings.DEBUG:
cmd_args = get_sandbox_args(cmd_args, whitelist=[os.path.dirname(path)], readonly=[os.path.dirname(path)])
@@ -61,4 +64,4 @@ def get_sandbox_args(
if extra_args:
firejail_args.extend(extra_args)
- return [*firejail_args, *cmd_args]
+ return [*firejail_args, *cmd_args]
\ No newline at end of file
diff --git a/othello/sandboxing/import_wrapper.py b/othello/sandboxing/import_wrapper.py
index f0205af69..8f3bddf69 100644
--- a/othello/sandboxing/import_wrapper.py
+++ b/othello/sandboxing/import_wrapper.py
@@ -26,4 +26,4 @@ def main() -> None:
if __name__ == "__main__":
- main()
+ main()
\ No newline at end of file
diff --git a/othello/settings/__init__.py b/othello/settings/__init__.py
index 42e6e6819..725802089 100644
--- a/othello/settings/__init__.py
+++ b/othello/settings/__init__.py
@@ -44,6 +44,7 @@
"othello.apps.auth.apps.AuthConfig",
"othello.apps.games.apps.GamesConfig",
"othello.apps.tournaments.apps.TournamentsConfig",
+ "othello.apps.rating.apps.RatingConfig"
]
MIDDLEWARE = [
diff --git a/othello/settings/secret.sample.py b/othello/settings/secret.sample.py
index 4c6734439..b33c42d12 100644
--- a/othello/settings/secret.sample.py
+++ b/othello/settings/secret.sample.py
@@ -39,4 +39,4 @@
FRONT_PAGE_MESSAGE = ""
# Message to display on every page. HTML not escaped, be careful.
-GLOBAL_MESSAGE = ""
+GLOBAL_MESSAGE = ""
\ No newline at end of file
diff --git a/othello/static/js/base.js b/othello/static/js/base.js
index a7e190266..857c900bb 100644
--- a/othello/static/js/base.js
+++ b/othello/static/js/base.js
@@ -24,6 +24,12 @@ tippy(".yourself", {
placement: "right"
})
+tippy(".score", {
+ content: "The score shown assumes that the game ended with 64 tokens on the board. " +
+ "This doesn't always happen, so the score here might be wrong.",
+ placement: "right"
+})
+
tippy("#include_users_file_help", {
content: "Upload a CSV file with each included user in a separate entry, users should be split by newline." +
" Users read from this file will be added to the above field.",
diff --git a/othello/templates/base.html b/othello/templates/base.html
index fd64f4578..8771acd8c 100644
--- a/othello/templates/base.html
+++ b/othello/templates/base.html
@@ -58,6 +58,20 @@
{% endif %}
+
+
+ Rating
+
+
+
{% if user|has_management_permissions %}
Admin
diff --git a/othello/templates/games/watch_list.html b/othello/templates/games/watch_list.html
index ee4e35bff..465afa902 100644
--- a/othello/templates/games/watch_list.html
+++ b/othello/templates/games/watch_list.html
@@ -18,6 +18,12 @@
{% if game.is_tournament %}
Tournament Game:
{% endif %}
+ {% if game.is_ranked %}
+ Ranked Game:
+ {% endif %}
+ {% if game.is_gauntlet %}
+ Gauntlet Game:
+ {% endif %}
{{ game }}
diff --git a/othello/templates/rating/gauntlet.html b/othello/templates/rating/gauntlet.html
new file mode 100644
index 000000000..6349a0963
--- /dev/null
+++ b/othello/templates/rating/gauntlet.html
@@ -0,0 +1,40 @@
+{% extends "base.html" %}
+{% block title %}Othello: Gauntlet{% endblock %}
+{% block main %}
+
+ The Gauntlet
+
+
+ Proceed below to run the gauntlet on your most recent submission.
+
+
+
+
+ {% if last_gauntlet %}
+
+ Most Recent Gauntlet
+
+
+ Submission: {{ last_gauntlet.submission.name }} (Verification Status: {% if last_gauntlet.submission.gauntlet %}Passed!{% else %}Failed{% endif %})
+
+ Game 1: {{ g1 }}
+
+ Game 2: {{ g2 }}
+
+ Game 3: {{ g3 }}
+
+
+ {% endif %}
+
+{% endblock %}
diff --git a/othello/templates/rating/gauntletrunning.html b/othello/templates/rating/gauntletrunning.html
new file mode 100644
index 000000000..3aaf9077d
--- /dev/null
+++ b/othello/templates/rating/gauntletrunning.html
@@ -0,0 +1,28 @@
+{% extends "base.html" %}
+{% block title %}Othello: Gauntlet{% endblock %}
+{% block main %}
+
+ The Gauntlet
+
+ {% if queue %}
+
+ Your gauntlet is in queue.
+
+ {% else %}
+
+ Your gauntlet is running.
+
+ Game 1: {{ g1 }}
+
+ Game 2: {{ g2 }}
+
+ Game 3: {{ g3 }}
+
+
+ Terminate and delete this gauntlet
+ {% endif %}
+
+
+
+{% endblock %}
diff --git a/othello/templates/rating/help.html b/othello/templates/rating/help.html
new file mode 100644
index 000000000..fa31e758e
--- /dev/null
+++ b/othello/templates/rating/help.html
@@ -0,0 +1,45 @@
+{% extends "base.html" %}
+{% block title %}Othello: About Rating{% endblock %}
+{% block main %}
+
+ Othello Rating
+
+
+ The rating is a metric used to measure an AI's performance. This server uses the Elo system.
+
+
+ How do I enter the rating?
+
+
+ Because computing power is limited, only AIs past a certain threshold of skill will be included in the rating.
+
+ Specifically, this means that for your AI to be entered in the rating, it must first qualify through passing the gauntlet. The gauntlet is a series of 3 benchmark AIs. To pass the gauntlet, one game will be played against each, and your AI will have to win 3/3.
+
+
+ How is rating calculated?
+
+
+ A modified Elo system is used to prioritize wins while also rewarding higher token differences. The exact rating change code is shown below:
+
+ def calculateElo(r1, r2, score):
+ # Let K = 32
+ # And r1 >= r2
+ assert r1 >= r2
+ p = (1 / (1 + 10 ** ((r1-r2) / 400)))
+ expected = 64 * (1-p) - 32
+
+ score = score*p + (32 if score > 0 else -32 if score < 0 else 0)
+
+ delta = (32 / 64) * (score - expected)
+ return round(delta)
+
+
+
+ How do ranked matches work?
+
+
+ Ranked matches will be run a few times a week. Tenatively, this is Monday, Wednesday, Friday at 4pm. You can view the next scheduled batch of rank games on the standings page.
+
+ For each ranked batch, qualified AIs will be matched against each other. Similarly rated AIs will be matched together. Each AI will play 2 games.
+
+{% endblock %}
diff --git a/othello/templates/rating/history.html b/othello/templates/rating/history.html
new file mode 100644
index 000000000..cb24acaaa
--- /dev/null
+++ b/othello/templates/rating/history.html
@@ -0,0 +1,40 @@
+{% extends "base.html" %}
+{% load static %}
+{% load extras %}
+{% block title %}Othello: Ranked Match History{% endblock %}
+{% block head %}
+
+{% endblock %}
+{% block main %}
+
+ Ranked Match History
+
+ Matches from the most recent ranked match batch are shown.
+
+
+
+
+ Time
+ Black
+ White
+ Score
+ Black Rating
+ White Rating
+
+
+
+
+ {% for game in games %}
+
+ {{game.created_at}}
+ {{game.black}}
+ {{game.white}}
+ {{game|parseScore}}
+ {{game|parseBlack}}
+ {{game|parseWhite}}
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/othello/templates/rating/manage.html b/othello/templates/rating/manage.html
new file mode 100644
index 000000000..cb7dca699
--- /dev/null
+++ b/othello/templates/rating/manage.html
@@ -0,0 +1,28 @@
+{% extends "base.html" %}
+{% block title %}Othello: Manage{% endblock %}
+{% block main %}
+
+ Admin Panel
+
+
+
+
+ Auto ranked games batch enabled: {{ manager.auto_run }}
+
+ Next auto run time: {{ manager.next_auto_run }}
+
+ Batch is running: {{ manager.running }}
+
+
+
+
+
+
+{% endblock %}
diff --git a/othello/templates/rating/standings.html b/othello/templates/rating/standings.html
new file mode 100644
index 000000000..a93981d02
--- /dev/null
+++ b/othello/templates/rating/standings.html
@@ -0,0 +1,44 @@
+{% extends "base.html" %}
+{% load static %}
+{% block title %}Othello: Standings{% endblock %}
+{% block head %}
+
+{% endblock %}
+{% block main %}
+
+ Overall Standings
+
+ The next batch of ranked matches is scheduled to be {{next_time}}
+
+
+
+
+ Ranking
+ User
+ Rating
+
+
+
+ {% for player in page_obj %}
+
+ {{ forloop.counter|add:offset }}
+ {{ player }}
+ {{ player.rating }}
+
+ {% endfor %}
+
+
+ {% if page_obj.has_other_pages %}
+
+ {% endif %}
+
+{% endblock %}
diff --git a/othello/templates/rating/throttle.html b/othello/templates/rating/throttle.html
new file mode 100644
index 000000000..ac814dcce
--- /dev/null
+++ b/othello/templates/rating/throttle.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% block title %}Othello: Gauntlet{% endblock %}
+{% block main %}
+
+ You are submitting too fast!
+
+ Please wait 1 minute in between running Gauntlets.
+
+{% endblock %}
diff --git a/othello/urls.py b/othello/urls.py
index 4c9136eda..05aa195c2 100644
--- a/othello/urls.py
+++ b/othello/urls.py
@@ -9,6 +9,7 @@
path("", include("othello.apps.games.urls", namespace="games")),
path("oauth/", include("social_django.urls", namespace="social")),
path("tournaments/", include("othello.apps.tournaments.urls", namespace="tournaments")),
+ path("rating/", include("othello.apps.rating.urls", namespace="rating"))
]
handler500 = handle_500_view
From 52d765f50aa1299d73eddb87b3124af5dc2e1de3 Mon Sep 17 00:00:00 2001
From: Johnny Liu
Date: Tue, 28 May 2024 11:22:21 -0400
Subject: [PATCH 2/6] fix condition
---
othello/apps/games/tasks.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/othello/apps/games/tasks.py b/othello/apps/games/tasks.py
index fe1a5f8e9..6aec46229 100644
--- a/othello/apps/games/tasks.py
+++ b/othello/apps/games/tasks.py
@@ -26,14 +26,14 @@ def send_through_game_channel(game: Game, event_type: str, object_id: int) -> in
def check_heartbeat(game: Game) -> bool:
- if game.is_tournament or game.is_ranked:
+ if game.is_tournament or game.is_ranked or game.is_gauntlet:
return True
game.refresh_from_db()
return (timezone.now() - game.last_heartbeat).seconds < settings.CLIENT_HEARTBEAT_INTERVAL * 2
def delete_game(game: Game) -> None:
- if not game.is_tournament and not game.is_ranked:
+ if not game.is_tournament and not game.is_ranked and not game.is_gauntlet:
game.delete()
From b192360330bcf15b80dc904309a72b3638258419 Mon Sep 17 00:00:00 2001
From: Johnny Liu
Date: Tue, 28 May 2024 11:40:52 -0400
Subject: [PATCH 3/6] fix gauntlet bot choice
---
othello/apps/rating/views.py | 23 +++++++++++++++++------
othello/templates/rating/help.html | 2 ++
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/othello/apps/rating/views.py b/othello/apps/rating/views.py
index 00bd930b8..aafd0eacd 100644
--- a/othello/apps/rating/views.py
+++ b/othello/apps/rating/views.py
@@ -16,6 +16,7 @@
from .tasks import runGauntlet, runAllScrims, deleteAllRankedGames, getNextScrimTime
from .models import Gauntlet, RankedManager
from .forms import MultipleChoiceForm
+from ..auth.models import User
logger = logging.getLogger("othello")
@@ -96,9 +97,15 @@ def gauntlet(request: HttpRequest) -> HttpResponse:
# keep one gauntlet run saved. since we're making a new one, delete the old one
submission = Submission.objects.filter(user=request.user).order_by("-created_at").first()
+
+ # INSERT THE CORRECT GAUNTLET BOT HERE
+ gauntletUser = User.objects.filter(username="2024jliu").first()
+ gauntletBot = Submission.objects.filter(user=gauntletUser).first()
+ # ------------------------------------
+
game1 = Game.objects.create(
- black=submission,
- white=submission,
+ black=gauntletBot,
+ white=gauntletBot,
time_limit=5,
playing=False,
last_heartbeat=timezone.now(),
@@ -106,8 +113,8 @@ def gauntlet(request: HttpRequest) -> HttpResponse:
is_gauntlet=True,
)
game2 = Game.objects.create(
- black=submission,
- white=submission,
+ black=gauntletBot,
+ white=gauntletBot,
time_limit=5,
playing=False,
last_heartbeat=timezone.now(),
@@ -115,8 +122,8 @@ def gauntlet(request: HttpRequest) -> HttpResponse:
is_gauntlet=True,
)
game3 = Game.objects.create(
- black=submission,
- white=submission,
+ black=gauntletBot,
+ white=gauntletBot,
time_limit=5,
playing=False,
last_heartbeat=timezone.now(),
@@ -143,6 +150,10 @@ def gauntlet(request: HttpRequest) -> HttpResponse:
myGauntlet.game3.black = submission
else:
myGauntlet.game3.white = submission
+
+ myGauntlet.game1.save()
+ myGauntlet.game2.save()
+ myGauntlet.game3.save()
myGauntlet.save()
gauntlets = Gauntlet.objects.all().filter(finished=False).order_by("created_at")
diff --git a/othello/templates/rating/help.html b/othello/templates/rating/help.html
index fa31e758e..8f052a169 100644
--- a/othello/templates/rating/help.html
+++ b/othello/templates/rating/help.html
@@ -42,4 +42,6 @@
For each ranked batch, qualified AIs will be matched against each other. Similarly rated AIs will be matched together. Each AI will play 2 games.
+
+ Brought to you by 2024jliu & 2024dwei
{% endblock %}
From f4d60be0d0cf2f8f60cea2ffc0cd2484c6e8a069 Mon Sep 17 00:00:00 2001
From: Johnny Liu
Date: Tue, 16 Jul 2024 19:54:06 -0400
Subject: [PATCH 4/6] celerybeat - raw code
---
Pipfile | 5 +-
Pipfile.lock | 344 ++++++------------
celerybeat-schedule.bak | 4 +
celerybeat-schedule.dat | Bin 0 -> 1540 bytes
celerybeat-schedule.dir | 4 +
othello/apps/rating/admin.py | 13 +
othello/apps/rating/forms.py | 2 +-
.../migrations/0016_alter_gauntlet_myside2.py | 18 +
.../migrations/0017_auto_20240716_1826.py | 24 ++
.../migrations/0018_auto_20240716_1826.py | 30 ++
.../migrations/0019_auto_20240716_1828.py | 25 ++
.../migrations/0020_auto_20240716_1829.py | 22 ++
.../migrations/0021_auto_20240716_1830.py | 23 ++
.../migrations/0022_auto_20240716_1831.py | 30 ++
.../migrations/0023_auto_20240716_1932.py | 34 ++
.../migrations/0024_auto_20240716_1934.py | 31 ++
.../migrations/0025_auto_20240716_1934.py | 23 ++
othello/apps/rating/models.py | 5 +-
othello/apps/rating/tasks.py | 182 ++++-----
othello/apps/rating/views.py | 54 +--
othello/moderator/runners.py | 130 ++-----
othello/sandboxing/__init__.py | 5 +-
othello/settings/__init__.py | 1 +
othello/templates/rating/manage.html | 6 +-
24 files changed, 543 insertions(+), 472 deletions(-)
create mode 100644 celerybeat-schedule.bak
create mode 100644 celerybeat-schedule.dat
create mode 100644 celerybeat-schedule.dir
create mode 100644 othello/apps/rating/migrations/0016_alter_gauntlet_myside2.py
create mode 100644 othello/apps/rating/migrations/0017_auto_20240716_1826.py
create mode 100644 othello/apps/rating/migrations/0018_auto_20240716_1826.py
create mode 100644 othello/apps/rating/migrations/0019_auto_20240716_1828.py
create mode 100644 othello/apps/rating/migrations/0020_auto_20240716_1829.py
create mode 100644 othello/apps/rating/migrations/0021_auto_20240716_1830.py
create mode 100644 othello/apps/rating/migrations/0022_auto_20240716_1831.py
create mode 100644 othello/apps/rating/migrations/0023_auto_20240716_1932.py
create mode 100644 othello/apps/rating/migrations/0024_auto_20240716_1934.py
create mode 100644 othello/apps/rating/migrations/0025_auto_20240716_1934.py
diff --git a/Pipfile b/Pipfile
index e490cfa4d..2760f828f 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,6 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
+celery = "~=5.2.2"
channels = "~=3.0.3"
channels-redis = "~=3.2.0"
daphne = "~=3.0.1"
@@ -19,9 +20,7 @@ service_identity = "~=18.1.0"
social-auth-app-django = "~=4.0.0"
sqlparse = "~=0.4.2"
pyopenssl = "*"
-colorama = "*"
-celery = "==5.2.7"
-gevent = "*"
+django-celery-beat = "*"
[dev-packages]
flake8 = "~=3.9.2"
diff --git a/Pipfile.lock b/Pipfile.lock
index d54850eb1..44c0137a9 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "7c3c92c539206eafe88f3e1102ba40fcf12eef38c3f55eeb6a339687e3ce29c3"
+ "sha256": "bc83ce8df76eb8a70bafd44d392b2b34f0667e25f911c5387fa8cc7292b6d45a"
},
"pipfile-spec": 6,
"requires": {},
@@ -103,7 +103,7 @@
"sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac",
"sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"
],
- "markers": "python_version < '3.9'",
+ "markers": "python_version >= '3.6'",
"version": "==0.2.1"
},
"billiard": {
@@ -111,7 +111,6 @@
"sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
"sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
],
- "markers": "python_version >= '3.7'",
"version": "==3.6.4.0"
},
"celery": {
@@ -125,11 +124,11 @@
},
"certifi": {
"hashes": [
- "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
- "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
+ "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b",
+ "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"
],
"markers": "python_version >= '3.6'",
- "version": "==2024.2.2"
+ "version": "==2024.7.4"
},
"cffi": {
"hashes": [
@@ -186,7 +185,7 @@
"sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956",
"sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"
],
- "markers": "platform_python_implementation == 'CPython' and sys_platform == 'win32'",
+ "markers": "platform_python_implementation != 'PyPy'",
"version": "==1.16.0"
},
"channels": {
@@ -334,15 +333,6 @@
"markers": "python_version >= '3.6'",
"version": "==0.3.0"
},
- "colorama": {
- "hashes": [
- "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
- "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
- ],
- "index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
- "version": "==0.4.6"
- },
"constantly": {
"hashes": [
"sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9",
@@ -351,43 +341,50 @@
"markers": "python_version >= '3.8'",
"version": "==23.10.4"
},
+ "cron-descriptor": {
+ "hashes": [
+ "sha256:7b1a00d7d25d6ae6896c0da4457e790b98cba778398a3d48e341e5e0d33f0488",
+ "sha256:a67ba21804983b1427ed7f3e1ec27ee77bf24c652b0430239c268c5ddfbf9dc0"
+ ],
+ "version": "==1.4.3"
+ },
"cryptography": {
"hashes": [
- "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55",
- "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785",
- "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b",
- "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886",
- "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82",
- "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1",
- "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda",
- "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f",
- "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68",
- "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60",
- "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7",
- "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd",
- "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582",
- "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc",
- "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858",
- "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b",
- "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2",
- "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678",
- "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13",
- "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4",
- "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8",
- "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604",
- "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477",
- "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e",
- "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a",
- "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9",
- "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14",
- "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda",
- "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da",
- "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562",
- "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2",
- "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"
+ "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad",
+ "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583",
+ "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b",
+ "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c",
+ "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1",
+ "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648",
+ "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949",
+ "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba",
+ "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c",
+ "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9",
+ "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d",
+ "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c",
+ "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e",
+ "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2",
+ "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d",
+ "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7",
+ "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70",
+ "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2",
+ "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7",
+ "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14",
+ "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe",
+ "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e",
+ "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71",
+ "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961",
+ "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7",
+ "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c",
+ "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28",
+ "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842",
+ "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902",
+ "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801",
+ "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a",
+ "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"
],
"markers": "python_version >= '3.7'",
- "version": "==42.0.7"
+ "version": "==42.0.8"
},
"daphne": {
"hashes": [
@@ -423,6 +420,13 @@
"markers": "python_version >= '3.6'",
"version": "==3.2.25"
},
+ "django-celery-beat": {
+ "hashes": [
+ "sha256:f75b2d129731f1214be8383e18fae6bfeacdb55dffb2116ce849222c0106f9ad"
+ ],
+ "index": "pypi",
+ "version": "==2.6.0"
+ },
"django-celery-results": {
"hashes": [
"sha256:75aa51970db5691cbf242c6a0ff50c8cdf419e265cd0e9b772335d06436c4b99",
@@ -440,6 +444,14 @@
"markers": "python_version >= '3.6'",
"version": "==3.1.5"
},
+ "django-timezone-field": {
+ "hashes": [
+ "sha256:3232e7ecde66ba4464abb6f9e6b8cc739b914efb9b29dc2cf2eee451f7cc2acb",
+ "sha256:aa6f4965838484317b7f08d22c0d91a53d64e7bbbd34264468ae83d4023898a7"
+ ],
+ "markers": "python_version >= '3.8' and python_version < '4.0'",
+ "version": "==7.0"
+ },
"executing": {
"hashes": [
"sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147",
@@ -448,118 +460,6 @@
"markers": "python_version >= '3.5'",
"version": "==2.0.1"
},
- "gevent": {
- "hashes": [
- "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5",
- "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de",
- "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8",
- "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5",
- "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc",
- "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800",
- "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe",
- "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7",
- "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9",
- "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533",
- "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc",
- "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056",
- "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6",
- "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026",
- "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40",
- "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07",
- "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e",
- "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be",
- "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8",
- "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5",
- "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1",
- "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789",
- "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19",
- "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5",
- "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7",
- "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388",
- "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8",
- "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98",
- "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3",
- "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7",
- "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060",
- "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d",
- "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661",
- "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c",
- "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb",
- "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f",
- "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91",
- "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0",
- "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f",
- "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836",
- "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.8'",
- "version": "==24.2.1"
- },
- "greenlet": {
- "hashes": [
- "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67",
- "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6",
- "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257",
- "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4",
- "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676",
- "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61",
- "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc",
- "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca",
- "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7",
- "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728",
- "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305",
- "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6",
- "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379",
- "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414",
- "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04",
- "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a",
- "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf",
- "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491",
- "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559",
- "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e",
- "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274",
- "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb",
- "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b",
- "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9",
- "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b",
- "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be",
- "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506",
- "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405",
- "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113",
- "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f",
- "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5",
- "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230",
- "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d",
- "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f",
- "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a",
- "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e",
- "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61",
- "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6",
- "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d",
- "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71",
- "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22",
- "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2",
- "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3",
- "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067",
- "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc",
- "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881",
- "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3",
- "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e",
- "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac",
- "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53",
- "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0",
- "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b",
- "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83",
- "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41",
- "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c",
- "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf",
- "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da",
- "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"
- ],
- "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'",
- "version": "==3.0.3"
- },
"hiredis": {
"hashes": [
"sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772",
@@ -808,6 +708,14 @@
"markers": "python_version >= '3.6'",
"version": "==0.8.4"
},
+ "pexpect": {
+ "hashes": [
+ "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523",
+ "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"
+ ],
+ "markers": "sys_platform != 'win32'",
+ "version": "==4.9.0"
+ },
"pickleshare": {
"hashes": [
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
@@ -817,11 +725,11 @@
},
"prompt-toolkit": {
"hashes": [
- "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d",
- "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"
+ "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10",
+ "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"
],
"markers": "python_full_version >= '3.7.0'",
- "version": "==3.0.43"
+ "version": "==3.0.47"
},
"psutil": {
"hashes": [
@@ -880,6 +788,13 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.6"
},
+ "ptyprocess": {
+ "hashes": [
+ "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
+ "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
+ ],
+ "version": "==0.7.0"
+ },
"pure-eval": {
"hashes": [
"sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350",
@@ -936,6 +851,21 @@
"markers": "python_version >= '3.7'",
"version": "==24.1.0"
},
+ "python-crontab": {
+ "hashes": [
+ "sha256:40067d1dd39ade3460b2ad8557c7651514cd3851deffff61c5c60e1227c5c36b",
+ "sha256:82cb9b6a312d41ff66fd3caf3eed7115c28c195bfb50711bc2b4b9592feb9fe5"
+ ],
+ "version": "==3.2.0"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
+ "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.9.0.post0"
+ },
"python3-openid": {
"hashes": [
"sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
@@ -962,11 +892,11 @@
},
"requests": {
"hashes": [
- "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289",
- "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"
+ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
+ "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
],
"markers": "python_version >= '3.8'",
- "version": "==2.32.2"
+ "version": "==2.32.3"
},
"requests-oauthlib": {
"hashes": [
@@ -978,12 +908,12 @@
},
"sentry-sdk": {
"hashes": [
- "sha256:139a71a19f5e9eb5d3623942491ce03cf8ebc14ea2e39ba3e6fe79560d8a5b1f",
- "sha256:c5aeb095ba226391d337dd42a6f9470d86c9fc236ecc71cfc7cd1942b45010c6"
+ "sha256:545fcc6e36c335faa6d6cda84669b6e17025f31efbf3b2211ec14efe008b75d1",
+ "sha256:87b3d413c87d8e7f816cc9334bff255a83d8b577db2b22042651c30c19c09190"
],
"index": "pypi",
"markers": "python_version >= '3.6'",
- "version": "==2.3.1"
+ "version": "==2.10.0"
},
"service-identity": {
"hashes": [
@@ -995,11 +925,11 @@
},
"setuptools": {
"hashes": [
- "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
- "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
+ "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5",
+ "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"
],
"markers": "python_version >= '3.8'",
- "version": "==70.0.0"
+ "version": "==70.3.0"
},
"six": {
"hashes": [
@@ -1061,31 +991,6 @@
"markers": "python_full_version >= '3.8.0'",
"version": "==24.3.0"
},
- "twisted-iocpsupport": {
- "hashes": [
- "sha256:0058c963c8957bcd3deda62122e89953c9de1e867a274facc9b15dde1a9f31e8",
- "sha256:0c1b5cf37f0b2d96cc3c9bc86fff16613b9f5d0ca565c96cf1f1fb8cfca4b81c",
- "sha256:196f7c7ccad4ba4d1783b1c4e1d1b22d93c04275cd780bf7498d16c77319ad6e",
- "sha256:300437af17396a945a58dcfffd77863303a8b6d9e65c6e81f1d2eed55b50d444",
- "sha256:391ac4d6002a80e15f35adc4ad6056f4fe1c17ceb0d1f98ba01b0f4f917adfd7",
- "sha256:3c5dc11d72519e55f727320e3cee535feedfaee09c0f0765ed1ca7badff1ab3c",
- "sha256:3d306fc4d88a6bcf61ce9d572c738b918578121bfd72891625fab314549024b5",
- "sha256:4574eef1f3bb81501fb02f911298af3c02fe8179c31a33b361dd49180c3e644d",
- "sha256:4e5f97bcbabdd79cbaa969b63439b89801ea560f11d42b0a387634275c633623",
- "sha256:6081bd7c2f4fcf9b383dcdb3b3385d75a26a7c9d2be25b6950c3d8ea652d2d2d",
- "sha256:76f7e67cec1f1d097d1f4ed7de41be3d74546e1a4ede0c7d56e775c4dce5dfb0",
- "sha256:7c66fa0aa4236b27b3c61cb488662d85dae746a6d1c7b0d91cf7aae118445adf",
- "sha256:858096c0d15e33f15ac157f455d8f86f2f2cdd223963e58c0f682a3af8362d89",
- "sha256:872747a3b64e2909aee59c803ccd0bceb9b75bf27915520ebd32d69687040fa2",
- "sha256:afa2b630797f9ed2f27f3d9f55e3f72b4244911e45a8c82756f44babbf0b243e",
- "sha256:c2712b778bacf1db434e3e065adfed3db300754186a29aecac1efae9ef4bcaff",
- "sha256:c27985e949b9b1a1fb4c20c71d315c10ea0f93fdf3ccdd4a8c158b5926edd8c8",
- "sha256:cc86c2ef598c15d824a243c2541c29459881c67fc3c0adb6efe2242f8f0ec3af",
- "sha256:e311dfcb470696e3c077249615893cada598e62fa7c4e4ca090167bd2b7d331f"
- ],
- "markers": "platform_system == 'Windows'",
- "version": "==1.0.4"
- },
"txaio": {
"hashes": [
"sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490",
@@ -1096,11 +1001,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8",
- "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"
+ "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
+ "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version < '3.10'",
- "version": "==4.12.0"
+ "version": "==4.12.2"
},
"tzdata": {
"hashes": [
@@ -1112,11 +1017,11 @@
},
"urllib3": {
"hashes": [
- "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
- "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
+ "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
+ "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
],
"markers": "python_version >= '3.8'",
- "version": "==2.2.1"
+ "version": "==2.2.2"
},
"vine": {
"hashes": [
@@ -1133,15 +1038,7 @@
],
"version": "==0.2.13"
},
- "zope.event": {
- "hashes": [
- "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26",
- "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==5.0"
- },
- "zope.interface": {
+ "zope-interface": {
"hashes": [
"sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854",
"sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b",
@@ -1247,15 +1144,6 @@
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
- "colorama": {
- "hashes": [
- "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
- "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
- ],
- "index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
- "version": "==0.4.6"
- },
"coverage": {
"hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
@@ -1532,11 +1420,11 @@
},
"setuptools": {
"hashes": [
- "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
- "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
+ "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5",
+ "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"
],
"markers": "python_version >= '3.8'",
- "version": "==70.0.0"
+ "version": "==70.3.0"
},
"sqlparse": {
"hashes": [
@@ -1601,11 +1489,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8",
- "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"
+ "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
+ "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version < '3.10'",
- "version": "==4.12.0"
+ "version": "==4.12.2"
},
"wrapt": {
"hashes": [
diff --git a/celerybeat-schedule.bak b/celerybeat-schedule.bak
new file mode 100644
index 000000000..5fad0d12c
--- /dev/null
+++ b/celerybeat-schedule.bak
@@ -0,0 +1,4 @@
+'entries', (0, 417)
+'__version__', (512, 15)
+'tz', (1024, 26)
+'utc_enabled', (1536, 4)
diff --git a/celerybeat-schedule.dat b/celerybeat-schedule.dat
new file mode 100644
index 0000000000000000000000000000000000000000..4c5fc0e0a95d4e6745659c41ff3126d149edf0b1
GIT binary patch
literal 1540
zcmeHG%T59@6dfEDsS5bQ_uB=`rbIEBgq87AT=)?r
zF8me$LJO!HcP1=MY!_{AdLH+*ZR-)VteMs{%@(eZV&8M^{To&nA!3}XQqZCm!6dol
zjrv+WA?*RFWMM6&eLhR^S$#g8+e`{stqbzK!SFGO
zzs1wzxk9o~!U*?U@}5{e!D!#rQC$R8&hi)H*1GnB8scZ2*Z24Hbkfw7`sv9P#v~F3
z$OFa=Oq8`R*Wst(knq53NLC@3%$C>6Lp|VLzKEn5QxtnhIVp*>fvE`7mE8z45oRUk
z3}noVI`EYO<2BK+E$#}|X!@9UR0i{Eezq`^%Ozf(vEV2tiw2g;nlqX0DHO2WTMw|}
zD6OvAs)4l#>s_^t2%BbH>4-bO8mQd2&O1+`#I}K*CRO!8j<#-&DE<;H(nDy2RvTm|
b|J=VxK;3`$@BjE^|4-pJY5qM-TYsb<_uhy@
literal 0
HcmV?d00001
diff --git a/celerybeat-schedule.dir b/celerybeat-schedule.dir
new file mode 100644
index 000000000..5fad0d12c
--- /dev/null
+++ b/celerybeat-schedule.dir
@@ -0,0 +1,4 @@
+'entries', (0, 417)
+'__version__', (512, 15)
+'tz', (1024, 26)
+'utc_enabled', (1536, 4)
diff --git a/othello/apps/rating/admin.py b/othello/apps/rating/admin.py
index e69de29bb..33374013d 100644
--- a/othello/apps/rating/admin.py
+++ b/othello/apps/rating/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from .models import RankedManager, Gauntlet
+
+
+class RankedManagerAdmin(admin.ModelAdmin):
+ readonly_fields = ("id",)
+
+class GauntletAdmin(admin.ModelAdmin):
+ readonly_fields = ("id",)
+
+admin.site.register(RankedManager, RankedManagerAdmin)
+admin.site.register(Gauntlet, GauntletAdmin)
\ No newline at end of file
diff --git a/othello/apps/rating/forms.py b/othello/apps/rating/forms.py
index fd38631c5..64b5a484f 100644
--- a/othello/apps/rating/forms.py
+++ b/othello/apps/rating/forms.py
@@ -2,11 +2,11 @@
class MultipleChoiceForm(forms.Form):
CHOICES = [
- ('runbatch', 'Manually run ranked games batch'),
('deletegames', 'Delete all ranked games models'),
('deletegauntlets', 'Delete all gauntlet models'),
('disableauto', 'Disable auto ranked games (also terminates if current)'),
('enableauto', 'Enable auto ranked games'),
+ ('initranked', 'Initialize ranked (when the server starts for the first time)')
]
choices = forms.ChoiceField(choices=CHOICES, widget=forms.RadioSelect)
diff --git a/othello/apps/rating/migrations/0016_alter_gauntlet_myside2.py b/othello/apps/rating/migrations/0016_alter_gauntlet_myside2.py
new file mode 100644
index 000000000..82a1c7956
--- /dev/null
+++ b/othello/apps/rating/migrations/0016_alter_gauntlet_myside2.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.24 on 2024-07-16 15:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0015_auto_20240528_0833'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0017_auto_20240716_1826.py b/othello/apps/rating/migrations/0017_auto_20240716_1826.py
new file mode 100644
index 000000000..c4fa2e31a
--- /dev/null
+++ b/othello/apps/rating/migrations/0017_auto_20240716_1826.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:26
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0036_game_is_gauntlet'),
+ ('rating', '0016_alter_gauntlet_myside2'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='rankedmanager',
+ name='celery_task_id',
+ ),
+ migrations.AddField(
+ model_name='rankedmanager',
+ name='game',
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='g', to='games.game'),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0018_auto_20240716_1826.py b/othello/apps/rating/migrations/0018_auto_20240716_1826.py
new file mode 100644
index 000000000..ea0133b22
--- /dev/null
+++ b/othello/apps/rating/migrations/0018_auto_20240716_1826.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:26
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0036_game_is_gauntlet'),
+ ('rating', '0017_auto_20240716_1826'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='rankedmanager',
+ name='game',
+ field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='g', to='games.game'),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0019_auto_20240716_1828.py b/othello/apps/rating/migrations/0019_auto_20240716_1828.py
new file mode 100644
index 000000000..7dd540ff5
--- /dev/null
+++ b/othello/apps/rating/migrations/0019_auto_20240716_1828.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:28
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0036_game_is_gauntlet'),
+ ('rating', '0018_auto_20240716_1826'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='rankedmanager',
+ name='game',
+ field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='g', to='games.game'),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0020_auto_20240716_1829.py b/othello/apps/rating/migrations/0020_auto_20240716_1829.py
new file mode 100644
index 000000000..9b1248d77
--- /dev/null
+++ b/othello/apps/rating/migrations/0020_auto_20240716_1829.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0019_auto_20240716_1828'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='rankedmanager',
+ name='game',
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0021_auto_20240716_1830.py b/othello/apps/rating/migrations/0021_auto_20240716_1830.py
new file mode 100644
index 000000000..b80d24a74
--- /dev/null
+++ b/othello/apps/rating/migrations/0021_auto_20240716_1830.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0020_auto_20240716_1829'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0022_auto_20240716_1831.py b/othello/apps/rating/migrations/0022_auto_20240716_1831.py
new file mode 100644
index 000000000..1068471a9
--- /dev/null
+++ b/othello/apps/rating/migrations/0022_auto_20240716_1831.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.2.25 on 2024-07-16 22:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('games', '0036_game_is_gauntlet'),
+ ('rating', '0021_auto_20240716_1830'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='rankedmanager',
+ name='game',
+ field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='g', to='games.game'),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0023_auto_20240716_1932.py b/othello/apps/rating/migrations/0023_auto_20240716_1932.py
new file mode 100644
index 000000000..fc9122639
--- /dev/null
+++ b/othello/apps/rating/migrations/0023_auto_20240716_1932.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.2.25 on 2024-07-16 23:32
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('django_celery_beat', '0018_improve_crontab_helptext'),
+ ('games', '0036_game_is_gauntlet'),
+ ('rating', '0022_auto_20240716_1831'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ProtectedPeriodicTask',
+ fields=[
+ ('periodictask_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_celery_beat.periodictask')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ bases=('django_celery_beat.periodictask',),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='rankedmanager',
+ name='game',
+ field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='g', to='games.game'),
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0024_auto_20240716_1934.py b/othello/apps/rating/migrations/0024_auto_20240716_1934.py
new file mode 100644
index 000000000..ca37e33ad
--- /dev/null
+++ b/othello/apps/rating/migrations/0024_auto_20240716_1934.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.25 on 2024-07-16 23:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0023_auto_20240716_1932'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide3',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ migrations.DeleteModel(
+ name='ProtectedPeriodicTask',
+ ),
+ ]
diff --git a/othello/apps/rating/migrations/0025_auto_20240716_1934.py b/othello/apps/rating/migrations/0025_auto_20240716_1934.py
new file mode 100644
index 000000000..8b94f15c0
--- /dev/null
+++ b/othello/apps/rating/migrations/0025_auto_20240716_1934.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-07-16 23:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rating', '0024_auto_20240716_1934'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide1',
+ field=models.CharField(default='x', max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='gauntlet',
+ name='mySide2',
+ field=models.CharField(default='o', max_length=1),
+ ),
+ ]
diff --git a/othello/apps/rating/models.py b/othello/apps/rating/models.py
index 8a6127e3d..b9b5b3f75 100644
--- a/othello/apps/rating/models.py
+++ b/othello/apps/rating/models.py
@@ -55,7 +55,7 @@ class RankedManager(models.Model):
auto_run = models.BooleanField(default=False, null=False)
next_auto_run = models.DateTimeField()
running = models.BooleanField(default=False, null=False)
- celery_task_id = models.CharField(max_length=48, default="")
+ game = models.ForeignKey(Game, on_delete=models.PROTECT, related_name="g", null=True, blank=True, default=None)
def __str__(self) -> str:
return f"Auto Run {self.auto_run}, Next Auto {self.next_auto_run}, Running {self.running}"
@@ -63,9 +63,6 @@ def __str__(self) -> str:
def __repr__(self) -> str:
return f"Auto Run {self.auto_run}, Next Auto {self.next_auto_run}, Running {self.running}"
-
-
-
# class RankedGame(models.Model):
# batch = models.DateField()
diff --git a/othello/apps/rating/tasks.py b/othello/apps/rating/tasks.py
index a9d40626a..6282ccc44 100644
--- a/othello/apps/rating/tasks.py
+++ b/othello/apps/rating/tasks.py
@@ -1,6 +1,8 @@
import logging, random, time, sys
from datetime import datetime, timedelta
from typing import Optional
+from django.utils import timezone
+
from asgiref.sync import async_to_sync
from celery import shared_task
@@ -170,123 +172,85 @@ def doPairing(n):
return pairs
-
def deleteAllRankedGames():
games = Game.objects.filter(is_ranked=True)
games.delete()
#games.save()
-def getNextScrimTime():
- #batches happen monday, wednesday, friday, 4pm
- today = datetime.today()
- mon = (-today.weekday() + 7) % 7
- wed = (2-today.weekday()+7) % 7
- fri = (4-today.weekday()+7) % 7
-
- if mon == 0 or wed == 0 or fri == 0:
- if datetime.now().hour >= 16: # if its after 4pm, then we dont want to run today
- if mon == 0: mon = 1000
- if wed == 0: wed = 1000
- if fri == 0: fri = 1000
-
- if mon < wed and mon < fri:
- logger.warning("Next run is monday")
- today += timedelta(days=mon)
- elif wed < mon and wed < fri:
- logger.warning("Next run is wednesday")
- today += timedelta(days=wed)
- elif fri < mon and fri < wed:
- logger.warning("Next run is friday")
- today += timedelta(days=fri)
-
- today = today.replace(hour=16, minute=0, second=0, microsecond=0)
-
- return today
+gameQueue = []
+lastTime = datetime.now()
+# will not run the first task, to prevent a bunch of tasks built up all being run at once
@shared_task
-def runAllScrims():
- manager = RankedManager.objects.first()
- if manager.running:
- logger.warning("Did not run a batch since one already is running")
- return
+def rankedSchedulerProcess():
+ global lastTime
- manager.running = True
- manager.save()
-
- deleteAllRankedGames()
+ if datetime.now() - lastTime < timedelta(seconds=45):
+ print("skipping")
+ return
- players = Submission.objects.rated()
- matches = doPairing(players.count())
- submissions = list(players)
-
- # print(matches, submissions)
-
- for round_matches in chunks(matches, 1 if sys.platform == "win32" else settings.CONCURRENT_GAME_LIMIT):
- games = []
-
- for i, j in round_matches:
- submissions[i].refresh_from_db()
- submissions[j].refresh_from_db()
- game = Game.objects.create(
- black=submissions[i],
- white=submissions[j],
- blackRating=submissions[i].rating,
- whiteRating=submissions[j].rating,
- time_limit=5,
- playing=True,
- is_ranked=True
- )
- games.append(game)
- logger.warning(f"Upcoming matches: {str(game)}")
- run_game.delay(game.id)
-
- # tasks = {game: run_game.delay(game.id) for game in games}
- # return
-
- for it in range(1000): # if this doesn't finish in 15 minutes, just moves on
- running = False
- for game in games:
- #tasks = {game: run_tournament_game.delay(game.id) for game in games}
- game.refresh_from_db()
- running = running or game.playing
- # print("check ", running)
- if not running: break
- time.sleep(1)
-
- running = False
- for game in games:
- running = running or game.playing
- if running:
- logger.warn("Some games didn't finish. Very bad.")
-
- for game in games:
- r1 = game.black.rating
- r2 = game.white.rating
- if r1 >= r2:
- delta = calculateElo(r1, r2, game.score)
- game.ratingDelta = delta
- r1 += delta
- r2 -= delta
- else:
- delta = calculateElo(r2, r1, -game.score)
- game.ratingDelta = -delta
- r1 -= delta
- r2 += delta
- game.black.rating = r1
- game.white.rating = r2
- game.black.save()
- game.white.save()
-
- game.save()
-
- manager.running = False
+ lastTime = datetime.now()
+
+ manager = RankedManager.objects.first()
+ if manager.game and not manager.game.playing:
+ game = manager.game
+ # the game finished, let's update ELO
+ r1 = game.black.rating
+ r2 = game.white.rating
+ if r1 >= r2:
+ delta = calculateElo(r1, r2, game.score)
+ game.ratingDelta = delta
+ r1 += delta
+ r2 -= delta
+ else:
+ delta = calculateElo(r2, r1, -game.score)
+ game.ratingDelta = -delta
+ r1 -= delta
+ r2 += delta
+ game.black.rating = r1
+ game.white.rating = r2
+ game.black.save()
+ game.white.save()
+ game.save()
+ manager.running = False
+ elif manager.game and manager.game.playing:
+ return
- # THIS IS NOT TESTED - has the precondition that this is the only task queued, so its safe to queue another runAllScrims
if manager.auto_run:
- logger.warning("queueing batch for later")
- manager.next_auto_run = getNextScrimTime()
- task = runAllScrims.apply_async([], eta=manager.next_auto_run)
- manager.celery_task_id = task.id
-
- manager.save()
- return
\ No newline at end of file
+ global gameQueue
+ if len(gameQueue) == 0: # then we have to queue the players!
+ players = Submission.objects.rated()
+ if players.count() < 2:
+ logger.warn("Too few players eligble for ranked!")
+ return
+ matches = doPairing(players.count())
+ submissions = list(players)
+ for tpl in matches:
+ gameQueue.append((submissions[tpl[0]], submissions[tpl[1]]))
+ logger.warn(f"Created game queue: {str(gameQueue)}")
+
+ if not manager.running:
+ black = gameQueue[-1][0]
+ white = gameQueue[-1][1]
+ logger.warn(f"Queueing ranked game --> {black} vs. {white}")
+
+ game = Game.objects.create(
+ black=black,
+ white=white,
+ blackRating=black.rating,
+ whiteRating=white.rating,
+ time_limit=5,
+ playing=True,
+ last_heartbeat=timezone.now(),
+ runoff=False,
+ is_ranked=True,
+ )
+
+ manager.game = game
+ manager.running = True
+ manager.save()
+
+ run_game.delay(manager.game.id)
+ gameQueue.pop()
+
+ manager.save()
\ No newline at end of file
diff --git a/othello/apps/rating/views.py b/othello/apps/rating/views.py
index aafd0eacd..64917fa58 100644
--- a/othello/apps/rating/views.py
+++ b/othello/apps/rating/views.py
@@ -13,11 +13,13 @@
from celery.result import AsyncResult
from ..games.models import Submission, Game
-from .tasks import runGauntlet, runAllScrims, deleteAllRankedGames, getNextScrimTime
+from .tasks import runGauntlet, deleteAllRankedGames
from .models import Gauntlet, RankedManager
from .forms import MultipleChoiceForm
from ..auth.models import User
+from django_celery_beat.models import PeriodicTask, IntervalSchedule
+
logger = logging.getLogger("othello")
def formatGameInfo(side, game):
@@ -99,7 +101,7 @@ def gauntlet(request: HttpRequest) -> HttpResponse:
submission = Submission.objects.filter(user=request.user).order_by("-created_at").first()
# INSERT THE CORRECT GAUNTLET BOT HERE
- gauntletUser = User.objects.filter(username="2024jliu").first()
+ gauntletUser = User.objects.filter(username="warden").first()
gauntletBot = Submission.objects.filter(user=gauntletUser).first()
# ------------------------------------
@@ -182,7 +184,7 @@ def deleteGauntlet(request: HttpRequest) -> HttpResponse:
def history(request: HttpRequest) -> HttpResponse:
if request.method == "GET":
- games = list(Game.objects.filter(is_ranked=True).order_by("-created_at"))
+ games = list(Game.objects.filter(is_ranked=True, playing=False).order_by("-created_at"))
return render(
request,
@@ -211,42 +213,44 @@ def standings(request: HttpRequest) -> HttpResponse:
@management_only
def manage(request: HttpRequest) -> HttpResponse:
manager = RankedManager.objects.first()
-
+
if request.method == 'POST':
form = MultipleChoiceForm(request.POST)
if form.is_valid():
selected_choice = form.cleaned_data['choices']
- #print(selected_choice)
- if selected_choice == 'runbatch':
- AsyncResult(manager.celery_task_id).revoke(terminate=True)
- if sys.platform == 'win32':
- runAllScrims()
- else:
- runAllScrims.delay()
- elif selected_choice == 'deletegames':
+ if selected_choice == 'deletegames':
deleteAllRankedGames()
elif selected_choice == 'deletegauntlets':
Gauntlet.objects.all().delete()
elif selected_choice == 'disableauto':
manager.auto_run = False
- AsyncResult(manager.celery_task_id).revoke(terminate=True)
- manager.save()
-
- # print(AsyncResult(manager.celery_task_id).failed())
- # print(AsyncResult(manager.celery_task_id).state)
-
manager.save()
elif selected_choice == 'enableauto':
manager.auto_run = True
manager.next_auto_run = getNextScrimTime()
+ manager.save()
+ elif selected_choice == 'initranked':
+ #create 1 single ranked manager object
+ RankedManager.objects.all().delete()
+ manager = RankedManager.objects.create(
+ auto_run = False,
+ running = False,
+ next_auto_run = timezone.now(),
+ )
- if AsyncResult(manager.celery_task_id).state != "PENDING":
- logger.warning("queueing batch for later")
- task = runAllScrims.apply_async([], eta=manager.next_auto_run)
- manager.celery_task_id = task.id
- manager.save()
-
-
+ #create scheduler that runs once a minute
+ interval, _ = IntervalSchedule.objects.get_or_create(
+ every=60,
+ period=IntervalSchedule.SECONDS,
+ )
+
+ PeriodicTask.objects.all().delete()
+ PeriodicTask.objects.create(
+ interval = interval,
+ name = "RankedScheduler",
+ task = "othello.apps.rating.tasks.rankedSchedulerProcess",
+ )
+ #celery -A othello beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
else:
form = MultipleChoiceForm()
return render(request, 'rating/manage.html', {'form': form, "manager": manager})
diff --git a/othello/moderator/runners.py b/othello/moderator/runners.py
index 6bddf61c5..ed11931cd 100644
--- a/othello/moderator/runners.py
+++ b/othello/moderator/runners.py
@@ -3,7 +3,6 @@
import signal
import subprocess
import time
-import sys
from typing import Generator, Tuple, Union
import psutil
@@ -45,10 +44,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
def start(self):
- if sys.platform == "win32":
- cmd_args = ["python", "-u", self.driver, self.path]
- else:
- cmd_args = ["python3", "-u", self.driver, self.path]
+ cmd_args = ["python3", "-u", self.driver, self.path]
if not settings.DEBUG:
cmd_args = get_sandbox_args(
cmd_args,
@@ -58,39 +54,26 @@ def start(self):
], # WARNING: Making the submission directory writable creates potential for extremely dangerous symlink attacks
)
- if sys.platform == "win32": #windows does not have os.setpgrp
- self.process = subprocess.Popen(
- cmd_args,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- bufsize=0,
- cwd=os.path.dirname(self.path),
- # preexec_fn=os.setpgrp,
- )
- else:
- self.process = subprocess.Popen(
- cmd_args,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- bufsize=0,
- cwd=os.path.dirname(self.path),
- preexec_fn=os.setpgrp,
- )
+ self.process = subprocess.Popen(
+ cmd_args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=0,
+ cwd=os.path.dirname(self.path),
+ preexec_fn=os.setpgrp,
+ )
def stop(self):
if self.process is not None:
try:
children = psutil.Process(self.process.pid).children(recursive=True)
- if sys.platform != "win32":
- os.killpg(self.process.pid, signal.SIGKILL)
+ os.killpg(self.process.pid, signal.SIGKILL)
except psutil.NoSuchProcess:
self.process = None
return
except ProcessLookupError:
self.process.kill()
-
for child in children:
try:
child.kill()
@@ -102,7 +85,6 @@ def stop(self):
def get_move(
self, board: str, player: Player, time_limit: int, last_move: Move
) -> Generator[str, None, Union[Tuple[int, int, int], Tuple[int, ServerError, int], Tuple[int, UserError, int]], ]:
-
if self.process.poll():
print(self.process.communicate())
return -1, ServerError.PROCESS_EXITED, -1
@@ -117,71 +99,31 @@ def get_move(
self.process.stdin.flush()
move, extra_time = -1, 0
- if sys.platform == "win32":
- #behold, chatgpt
- def possible_moves(board_str, player):
- def is_on_board(x, y):
- return 0 <= x < 8 and 0 <= y < 8
-
- def opponent(player):
- return 'o' if player == 'x' else 'x'
-
- # Convert the single string into a 2D list (8x8 board)
- board = [list(board_str[i:i + 8]) for i in range(0, 64, 8)]
- directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]
- possible_moves = []
-
- for x in range(8):
- for y in range(8):
- if board[x][y] == '.':
- for dx, dy in directions:
- nx, ny = x + dx, y + dy
- if is_on_board(nx, ny) and board[nx][ny] == opponent(player):
- while is_on_board(nx, ny) and board[nx][ny] == opponent(player):
- nx += dx
- ny += dy
- if is_on_board(nx, ny) and board[nx][ny] == player:
- possible_moves.append(y+x*8)
- break
-
- return possible_moves
-
- import random
- move = random.choice(possible_moves(board, player))
-
- if self.is_legacy:
- if move not in MOVES_10x10:
- return -1, UserError.READ_INVALID, -1
- else:
- if move < 0 or move >= 64:
+ start, total_timeout = time.time(), time_limit + 10
+ while move == -1:
+ if self.process.poll():
+ print(self.process.communicate())
+ return -1, ServerError.PROCESS_EXITED, -1
+ if (timeout := total_timeout - (time.time() - start)) <= 0:
+ return -1, ServerError.TIMEOUT, -1
+
+ files_ready = select.select([self.process.stdout, self.process.stderr], [], [], timeout)[0]
+ if self.process.stderr in files_ready:
+ yield self.process.stderr.read(8192).decode("latin-1")
+ if self.process.stdout in files_ready:
+ try:
+ parts = self.process.stdout.readline().decode("latin-1").split(";")
+ move, extra_time = int(parts[0]), int(parts[1])
+ print(f"GOT MOVE {player} {move};{extra_time}")
+
+ if self.is_legacy:
+ if move not in MOVES_10x10:
+ return -1, UserError.READ_INVALID, -1
+ else:
+ if move < 0 or move >= 64:
+ return -1, UserError.READ_INVALID, -1
+ except (ValueError, IndexError):
return -1, UserError.READ_INVALID, -1
- else:
- start, total_timeout = time.time(), time_limit + 10
- while move == -1:
- if self.process.poll():
- print(self.process.communicate())
- return -1, ServerError.PROCESS_EXITED, -1
- if (timeout := total_timeout - (time.time() - start)) <= 0:
- return -1, ServerError.TIMEOUT, -1
-
- files_ready = select.select([self.process.stdout, self.process.stderr], [], [], timeout)[0]
- if self.process.stderr in files_ready:
- yield self.process.stderr.read(8192).decode("latin-1")
- if self.process.stdout in files_ready:
- try:
- parts = self.process.stdout.readline().decode("latin-1").split(";")
- move, extra_time = int(parts[0]), int(parts[1])
- print(f"GOT MOVE {player} {move};{extra_time}")
-
- if self.is_legacy:
- if move not in MOVES_10x10:
- return -1, UserError.READ_INVALID, -1
- else:
- if move < 0 or move >= 64:
- return -1, UserError.READ_INVALID, -1
- except (ValueError, IndexError):
- return -1, UserError.READ_INVALID, -1
-
return (move, 0, extra_time) if not self.is_legacy else (MOVES_10x10[move], 0, extra_time)
@@ -216,4 +158,4 @@ def get_move(
m.delete()
return m.move, 0, -1
else:
- return -1, ServerError.DISCONNECT, -1
+ return -1, ServerError.DISCONNECT, -1
\ No newline at end of file
diff --git a/othello/sandboxing/__init__.py b/othello/sandboxing/__init__.py
index fba4ea05a..3a5e63c17 100644
--- a/othello/sandboxing/__init__.py
+++ b/othello/sandboxing/__init__.py
@@ -1,6 +1,6 @@
import json
import logging
-import os, sys
+import os
import subprocess
import traceback
from typing import Dict, List, Optional
@@ -11,9 +11,6 @@
def import_strategy_sandboxed(path: str) -> Optional[Dict[str, str]]:
- if sys.platform == "win32":
- return None
-
cmd_args = ["python3", "-u", settings.IMPORT_DRIVER, path]
if not settings.DEBUG:
cmd_args = get_sandbox_args(cmd_args, whitelist=[os.path.dirname(path)], readonly=[os.path.dirname(path)])
diff --git a/othello/settings/__init__.py b/othello/settings/__init__.py
index 725802089..27dee51a1 100644
--- a/othello/settings/__init__.py
+++ b/othello/settings/__init__.py
@@ -30,6 +30,7 @@
INSTALLED_APPS = [
"othello.apps.OthelloAdminConfig",
+ "django_celery_beat",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
diff --git a/othello/templates/rating/manage.html b/othello/templates/rating/manage.html
index cb7dca699..8c7dceaaa 100644
--- a/othello/templates/rating/manage.html
+++ b/othello/templates/rating/manage.html
@@ -7,11 +7,9 @@
- Auto ranked games batch enabled: {{ manager.auto_run }}
+ Auto ranked games running enabled: {{ manager.auto_run }}
- Next auto run time: {{ manager.next_auto_run }}
-
- Batch is running: {{ manager.running }}
+ Ranked game is running: {{ manager.running }}