ezquiz 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ezquiz-0.2.0/.gitignore +10 -0
- ezquiz-0.2.0/.python-version +1 -0
- ezquiz-0.2.0/PKG-INFO +9 -0
- ezquiz-0.2.0/README.md +0 -0
- ezquiz-0.2.0/pyproject.toml +18 -0
- ezquiz-0.2.0/src/ezquiz/__init__.py +4 -0
- ezquiz-0.2.0/src/ezquiz/apigame.py +86 -0
- ezquiz-0.2.0/src/ezquiz/cligame.py +16 -0
- ezquiz-0.2.0/src/ezquiz/ezquiz.py +34 -0
- ezquiz-0.2.0/src/ezquiz/py.typed +0 -0
- ezquiz-0.2.0/src/ezquiz/templates/index.html +419 -0
- ezquiz-0.2.0/tests/basic_addition.py +50 -0
- ezquiz-0.2.0/uv.lock +246 -0
ezquiz-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
ezquiz-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ezquiz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Alejandro Rodriguez <alejandro.rodriguez@alexanderthamm.com>
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Requires-Dist: fastapi>=0.128.0
|
|
8
|
+
Requires-Dist: jinja2>=3.1.6
|
|
9
|
+
Requires-Dist: uvicorn>=0.40.0
|
ezquiz-0.2.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ezquiz"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Alejandro Rodriguez", email = "alejandro.rodriguez@alexanderthamm.com" },
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"fastapi>=0.128.0",
|
|
12
|
+
"jinja2>=3.1.6",
|
|
13
|
+
"uvicorn>=0.40.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from random import choice
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import uvicorn
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
8
|
+
from fastapi.templating import Jinja2Templates
|
|
9
|
+
|
|
10
|
+
from ezquiz.ezquiz import Q
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class APIGame:
|
|
14
|
+
def __init__(self, title: str, qs: dict[str, Q]) -> None:
|
|
15
|
+
self.title = title
|
|
16
|
+
self.qs = qs
|
|
17
|
+
self.score = 0
|
|
18
|
+
self.mistakes = 0
|
|
19
|
+
self.streak = 0
|
|
20
|
+
self.history = []
|
|
21
|
+
|
|
22
|
+
def start(
|
|
23
|
+
self,
|
|
24
|
+
mode: Literal["timed", "score", "mistakes", "total", "streak"] = "score",
|
|
25
|
+
param=10,
|
|
26
|
+
):
|
|
27
|
+
app = FastAPI()
|
|
28
|
+
|
|
29
|
+
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
|
|
30
|
+
|
|
31
|
+
@app.get("/", response_class=HTMLResponse)
|
|
32
|
+
async def landing_page(request: Request):
|
|
33
|
+
return templates.TemplateResponse(
|
|
34
|
+
"index.html",
|
|
35
|
+
context={
|
|
36
|
+
"request": request,
|
|
37
|
+
"title": self.title,
|
|
38
|
+
"categories": list(self.qs.keys()),
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@app.post("/api/next", response_class=JSONResponse)
|
|
43
|
+
async def next_question(request: Request):
|
|
44
|
+
"""Get the next question in the session."""
|
|
45
|
+
# return a random question from selected categories
|
|
46
|
+
data = await request.json()
|
|
47
|
+
print(data)
|
|
48
|
+
categories = data.get("categories", [])
|
|
49
|
+
|
|
50
|
+
# Get all available questions from selected categories
|
|
51
|
+
cat = choice(categories)
|
|
52
|
+
|
|
53
|
+
q = self.qs[cat]
|
|
54
|
+
seed = q.get_seed()
|
|
55
|
+
prompt = q.ask(seed)
|
|
56
|
+
|
|
57
|
+
return JSONResponse(
|
|
58
|
+
{
|
|
59
|
+
"complete": False,
|
|
60
|
+
"question": {"category": cat, "seed": seed, "text": prompt},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@app.post("/api/submit", response_class=JSONResponse)
|
|
65
|
+
async def submit_answer(request: Request):
|
|
66
|
+
data = await request.json()
|
|
67
|
+
print(data)
|
|
68
|
+
cat = data["category"]
|
|
69
|
+
seed = data["seed"]
|
|
70
|
+
submitted_ans = data["answer"]
|
|
71
|
+
|
|
72
|
+
q = self.qs[cat]
|
|
73
|
+
correct_ans = q.correct(seed)
|
|
74
|
+
correct = q.check(correct_ans, submitted_ans)
|
|
75
|
+
explain = q.explain(seed)
|
|
76
|
+
|
|
77
|
+
return JSONResponse(
|
|
78
|
+
{
|
|
79
|
+
"correct": correct,
|
|
80
|
+
"submitted_answer": submitted_ans,
|
|
81
|
+
"correct_answer": correct_ans,
|
|
82
|
+
"explanation": explain,
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
uvicorn.run(app)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CLIGame:
|
|
5
|
+
def __init__(self) -> None:
|
|
6
|
+
self.score = 0
|
|
7
|
+
self.mistakes = 0
|
|
8
|
+
self.streak = 0
|
|
9
|
+
self.history = []
|
|
10
|
+
|
|
11
|
+
def start(
|
|
12
|
+
self,
|
|
13
|
+
mode: Literal["timed", "score", "mistakes", "total", "streak"] = "score",
|
|
14
|
+
param=10,
|
|
15
|
+
):
|
|
16
|
+
pass
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Callable, Generic, Literal, TypedDict, TypeVar
|
|
2
|
+
|
|
3
|
+
T = TypeVar("T")
|
|
4
|
+
|
|
5
|
+
Prompt = TypedDict("Prompt", {"type": Literal["text", "audio"], "value": str})
|
|
6
|
+
|
|
7
|
+
Explain = TypedDict("Explain", {"type": Literal["text", "text_diff"], "value": str})
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Q(Generic[T]):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
get_seed: Callable[[], T],
|
|
14
|
+
ask: Callable[[T], str],
|
|
15
|
+
correct: Callable[[T], str],
|
|
16
|
+
check: Callable[[T, str], bool] | None = None,
|
|
17
|
+
explain: Callable[[T], Explain] | None = None,
|
|
18
|
+
):
|
|
19
|
+
self.get_seed = get_seed
|
|
20
|
+
self.ask = ask
|
|
21
|
+
self.correct = correct
|
|
22
|
+
|
|
23
|
+
if check is None:
|
|
24
|
+
self.check = (
|
|
25
|
+
lambda correct_ans, submitted_ans: str(correct_ans) == submitted_ans
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
self.check = check
|
|
29
|
+
|
|
30
|
+
if explain is None:
|
|
31
|
+
self.explain = lambda _: "{textdiff}"
|
|
32
|
+
|
|
33
|
+
else:
|
|
34
|
+
self.explain = explain
|
|
File without changes
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body class="bg-gray-50 min-h-screen">
|
|
10
|
+
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
|
11
|
+
<!-- Initial Setup View -->
|
|
12
|
+
<div id="setup-view" class="bg-white rounded-lg shadow-md p-6">
|
|
13
|
+
<h1 class="text-3xl font-bold text-gray-800 mb-6 text-center">{{ title }}</h1>
|
|
14
|
+
|
|
15
|
+
<div class="mb-6">
|
|
16
|
+
<h2 class="text-lg font-semibold text-gray-700 mb-3">Select Categories:</h2>
|
|
17
|
+
<div class="space-y-2" id="categories-container">
|
|
18
|
+
{% for value in categories %}
|
|
19
|
+
<label class="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50 cursor-pointer transition">
|
|
20
|
+
<input type="checkbox" name="categories" value="{{ value }}"
|
|
21
|
+
class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 category-checkbox">
|
|
22
|
+
<span class="text-gray-700">{{ value }}</span>
|
|
23
|
+
</label>
|
|
24
|
+
{% endfor %}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<button id="start-btn"
|
|
29
|
+
class="w-full bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg hover:bg-blue-700 transition duration-200">
|
|
30
|
+
Start Quiz
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Quiz View with Sidebar -->
|
|
35
|
+
<div id="quiz-view" class="hidden">
|
|
36
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
37
|
+
<!-- Sidebar with Categories -->
|
|
38
|
+
<div class="md:col-span-1">
|
|
39
|
+
<div class="bg-white rounded-lg shadow-md p-4 sticky top-4">
|
|
40
|
+
<h3 class="font-semibold text-gray-700 mb-3">Categories</h3>
|
|
41
|
+
<div class="space-y-2" id="sidebar-categories">
|
|
42
|
+
{% for value in categories %}
|
|
43
|
+
<label class="flex items-center space-x-2 cursor-pointer">
|
|
44
|
+
<input type="checkbox" name="sidebar-categories" value="{{ value }}"
|
|
45
|
+
class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500 sidebar-category-checkbox">
|
|
46
|
+
<span class="text-sm text-gray-600">{{ value }}</span>
|
|
47
|
+
</label>
|
|
48
|
+
{% endfor %}
|
|
49
|
+
</div>
|
|
50
|
+
<button id="update-categories-btn"
|
|
51
|
+
class="w-full mt-4 bg-gray-600 text-white text-sm font-semibold py-2 px-4 rounded hover:bg-gray-700 transition duration-200">
|
|
52
|
+
Update
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Main Quiz Content -->
|
|
58
|
+
<div class="md:col-span-3">
|
|
59
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
60
|
+
<div class="flex justify-between items-center mb-6">
|
|
61
|
+
<span id="question-counter" class="text-sm text-gray-500">Question 1</span>
|
|
62
|
+
<span id="selected-categories-display" class="text-xs text-gray-400"></span>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Question Container -->
|
|
66
|
+
<div id="question-container" class="mb-6">
|
|
67
|
+
<p id="question-text" class="text-xl text-gray-800 mb-4"></p>
|
|
68
|
+
<input type="text" id="answer-input"
|
|
69
|
+
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
70
|
+
placeholder="Your answer...">
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<button id="submit-btn"
|
|
74
|
+
class="w-full bg-green-600 text-white font-semibold py-3 px-6 rounded-lg hover:bg-green-700 transition duration-200">
|
|
75
|
+
Submit Answer (Enter)
|
|
76
|
+
</button>
|
|
77
|
+
|
|
78
|
+
<!-- Result Container (initially hidden) -->
|
|
79
|
+
<div id="result-container" class="mt-6 hidden">
|
|
80
|
+
<!-- Result content will be inserted here -->
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.diff {
|
|
90
|
+
font-family: "Courier New", monospace;
|
|
91
|
+
font-size: 1.15em;
|
|
92
|
+
white-space: pre;
|
|
93
|
+
margin-top: 1rem;
|
|
94
|
+
}
|
|
95
|
+
.diff-row {
|
|
96
|
+
margin-bottom: 2px;
|
|
97
|
+
line-height: 1.2;
|
|
98
|
+
}
|
|
99
|
+
.diff-green {
|
|
100
|
+
color: #2e7d32;
|
|
101
|
+
font-weight: bold;
|
|
102
|
+
}
|
|
103
|
+
.diff-red {
|
|
104
|
+
color: #c62828;
|
|
105
|
+
font-weight: bold;
|
|
106
|
+
}
|
|
107
|
+
.diff-grey {
|
|
108
|
+
color: #9e9e9e;
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
111
|
+
<script>
|
|
112
|
+
// Store selected categories globally
|
|
113
|
+
let selectedCategories = [];
|
|
114
|
+
let currentQuestion = null;
|
|
115
|
+
let questionNumber = 0;
|
|
116
|
+
let showingResult = false;
|
|
117
|
+
|
|
118
|
+
// Text diff functions
|
|
119
|
+
function escapeChar(c) {
|
|
120
|
+
return c === " " ? " " : c;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function alignStrings(a, b) {
|
|
124
|
+
// Handle null/undefined inputs
|
|
125
|
+
a = a || "";
|
|
126
|
+
b = b || "";
|
|
127
|
+
|
|
128
|
+
const dp = Array.from({ length: a.length + 1 }, () =>
|
|
129
|
+
Array(b.length + 1).fill(0),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i <= a.length; i++) dp[i][0] = i;
|
|
133
|
+
for (let j = 0; j <= b.length; j++) dp[0][j] = j;
|
|
134
|
+
|
|
135
|
+
for (let i = 1; i <= a.length; i++) {
|
|
136
|
+
for (let j = 1; j <= b.length; j++) {
|
|
137
|
+
dp[i][j] =
|
|
138
|
+
a[i - 1] === b[j - 1]
|
|
139
|
+
? dp[i - 1][j - 1]
|
|
140
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let i = a.length,
|
|
145
|
+
j = b.length;
|
|
146
|
+
let aAlign = "",
|
|
147
|
+
bAlign = "";
|
|
148
|
+
|
|
149
|
+
while (i > 0 || j > 0) {
|
|
150
|
+
if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
|
|
151
|
+
aAlign = a[i - 1] + aAlign;
|
|
152
|
+
bAlign = b[j - 1] + bAlign;
|
|
153
|
+
i--;
|
|
154
|
+
j--;
|
|
155
|
+
} else if (i > 0 && j > 0 && dp[i][j] === dp[i - 1][j - 1] + 1) {
|
|
156
|
+
aAlign = a[i - 1] + aAlign;
|
|
157
|
+
bAlign = b[j - 1] + bAlign;
|
|
158
|
+
i--;
|
|
159
|
+
j--;
|
|
160
|
+
} else if (i > 0 && dp[i][j] === dp[i - 1][j] + 1) {
|
|
161
|
+
aAlign = a[i - 1] + aAlign;
|
|
162
|
+
bAlign = " " + bAlign;
|
|
163
|
+
i--;
|
|
164
|
+
} else {
|
|
165
|
+
aAlign = " " + aAlign;
|
|
166
|
+
bAlign = b[j - 1] + bAlign;
|
|
167
|
+
j--;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { aAlign, bAlign };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderTextDiff(userAnswer, correctAnswer) {
|
|
175
|
+
// Handle null/undefined inputs
|
|
176
|
+
userAnswer = String(userAnswer || "");
|
|
177
|
+
correctAnswer = String(correctAnswer || "");
|
|
178
|
+
|
|
179
|
+
const { aAlign, bAlign } = alignStrings(userAnswer, correctAnswer);
|
|
180
|
+
|
|
181
|
+
let top = "";
|
|
182
|
+
let bottom = "";
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < aAlign.length; i++) {
|
|
185
|
+
const a = aAlign[i];
|
|
186
|
+
const b = bAlign[i];
|
|
187
|
+
|
|
188
|
+
// TOP ROW (user input)
|
|
189
|
+
if (a === " " && b !== " ") {
|
|
190
|
+
top += `<span class="diff-grey">-</span>`;
|
|
191
|
+
} else if (a === b) {
|
|
192
|
+
top += `<span class="diff-green">${escapeChar(a)}</span>`;
|
|
193
|
+
} else if (a !== " ") {
|
|
194
|
+
top += `<span class="diff-red">${escapeChar(a)}</span>`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// BOTTOM ROW (reference)
|
|
198
|
+
if (b === " ") {
|
|
199
|
+
bottom += " ";
|
|
200
|
+
} else if (a === b) {
|
|
201
|
+
bottom += `<span class="diff-green">${escapeChar(b)}</span>`;
|
|
202
|
+
} else {
|
|
203
|
+
bottom += `<span class="diff-grey">${escapeChar(b)}</span>`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return `<div class="diff"><div class="diff-row">${top}</div><div class="diff-row">${bottom}</div></div>`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get DOM elements
|
|
211
|
+
const setupView = document.getElementById('setup-view');
|
|
212
|
+
const quizView = document.getElementById('quiz-view');
|
|
213
|
+
const startBtn = document.getElementById('start-btn');
|
|
214
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
215
|
+
const updateCategoriesBtn = document.getElementById('update-categories-btn');
|
|
216
|
+
const questionText = document.getElementById('question-text');
|
|
217
|
+
const answerInput = document.getElementById('answer-input');
|
|
218
|
+
const resultContainer = document.getElementById('result-container');
|
|
219
|
+
const questionCounter = document.getElementById('question-counter');
|
|
220
|
+
const selectedCategoriesDisplay = document.getElementById('selected-categories-display');
|
|
221
|
+
const questionContainer = document.getElementById('question-container');
|
|
222
|
+
|
|
223
|
+
// Start button click handler
|
|
224
|
+
startBtn.addEventListener('click', async () => {
|
|
225
|
+
// Get selected categories
|
|
226
|
+
const checkboxes = document.querySelectorAll('.category-checkbox:checked');
|
|
227
|
+
selectedCategories = Array.from(checkboxes).map(cb => cb.value);
|
|
228
|
+
|
|
229
|
+
if (selectedCategories.length === 0) {
|
|
230
|
+
alert('Please select at least one category');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Sync sidebar checkboxes
|
|
235
|
+
syncSidebarCategories();
|
|
236
|
+
|
|
237
|
+
// Fetch first question
|
|
238
|
+
await fetchNextQuestion();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Update categories button click handler
|
|
242
|
+
updateCategoriesBtn.addEventListener('click', async () => {
|
|
243
|
+
const checkboxes = document.querySelectorAll('.sidebar-category-checkbox:checked');
|
|
244
|
+
selectedCategories = Array.from(checkboxes).map(cb => cb.value);
|
|
245
|
+
|
|
246
|
+
if (selectedCategories.length === 0) {
|
|
247
|
+
alert('Please select at least one category');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fetch next question with updated categories
|
|
252
|
+
await fetchNextQuestion();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Sync sidebar checkboxes with selected categories
|
|
256
|
+
function syncSidebarCategories() {
|
|
257
|
+
document.querySelectorAll('.sidebar-category-checkbox').forEach(cb => {
|
|
258
|
+
cb.checked = selectedCategories.includes(cb.value);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Fetch next question
|
|
263
|
+
async function fetchNextQuestion() {
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetch('/api/next', {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json'
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify({ categories: selectedCategories })
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
|
|
275
|
+
if (data.complete) {
|
|
276
|
+
// Quiz complete
|
|
277
|
+
alert('Quiz complete! Great job!');
|
|
278
|
+
resetQuiz();
|
|
279
|
+
} else {
|
|
280
|
+
currentQuestion = data.question;
|
|
281
|
+
questionNumber++;
|
|
282
|
+
showQuestion();
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error('Error fetching question:', error);
|
|
286
|
+
alert('Failed to load question. Please try again.');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Submit button click handler
|
|
291
|
+
submitBtn.addEventListener('click', async () => {
|
|
292
|
+
if (showingResult) {
|
|
293
|
+
// If showing result, Enter goes to next question
|
|
294
|
+
await fetchNextQuestion();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const answer = answerInput.value.trim();
|
|
299
|
+
|
|
300
|
+
if (!answer) {
|
|
301
|
+
alert('Please enter an answer');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const response = await fetch('/api/submit', {
|
|
307
|
+
method: 'POST',
|
|
308
|
+
headers: {
|
|
309
|
+
'Content-Type': 'application/json'
|
|
310
|
+
},
|
|
311
|
+
body: JSON.stringify({
|
|
312
|
+
category: currentQuestion.category,
|
|
313
|
+
seed: currentQuestion.seed,
|
|
314
|
+
answer: answer,
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const data = await response.json();
|
|
319
|
+
showResult(data);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('Error submitting answer:', error);
|
|
322
|
+
alert('Failed to submit answer. Please try again.');
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Show question view
|
|
327
|
+
function showQuestion() {
|
|
328
|
+
setupView.classList.add('hidden');
|
|
329
|
+
quizView.classList.remove('hidden');
|
|
330
|
+
|
|
331
|
+
// Reset UI
|
|
332
|
+
resultContainer.classList.add('hidden');
|
|
333
|
+
resultContainer.innerHTML = '';
|
|
334
|
+
questionContainer.classList.remove('hidden');
|
|
335
|
+
submitBtn.textContent = 'Submit Answer (Enter)';
|
|
336
|
+
showingResult = false;
|
|
337
|
+
|
|
338
|
+
questionText.textContent = currentQuestion.text;
|
|
339
|
+
questionCounter.textContent = `Question ${questionNumber}`;
|
|
340
|
+
selectedCategoriesDisplay.textContent = selectedCategories.join(', ');
|
|
341
|
+
answerInput.value = '';
|
|
342
|
+
answerInput.focus();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Show result view
|
|
346
|
+
function showResult(data) {
|
|
347
|
+
showingResult = true;
|
|
348
|
+
questionContainer.classList.add('hidden');
|
|
349
|
+
resultContainer.classList.remove('hidden');
|
|
350
|
+
submitBtn.textContent = 'Next Question (Enter)';
|
|
351
|
+
|
|
352
|
+
const isCorrect = data.correct;
|
|
353
|
+
const bgColor = isCorrect ? 'bg-green-100' : 'bg-red-100';
|
|
354
|
+
const textColor = isCorrect ? 'text-green-800' : 'text-red-800';
|
|
355
|
+
const icon = isCorrect ? '✓' : '✗';
|
|
356
|
+
const message = isCorrect ? 'Correct!' : 'Incorrect';
|
|
357
|
+
|
|
358
|
+
// Check if explanation is textdiff placeholder
|
|
359
|
+
const isTextDiff = data.explanation === "{textdiff}";
|
|
360
|
+
|
|
361
|
+
let explanationHtml = '';
|
|
362
|
+
if (!isCorrect && isTextDiff) {
|
|
363
|
+
explanationHtml = `
|
|
364
|
+
<div class="bg-gray-50 border-l-4 border-gray-400 p-4">
|
|
365
|
+
${renderTextDiff(data.submitted_answer, data.correct_answer)}
|
|
366
|
+
</div>
|
|
367
|
+
`;
|
|
368
|
+
} else if (!isCorrect && data.explanation) {
|
|
369
|
+
explanationHtml = `<div class="bg-blue-50 border-l-4 border-blue-500 p-4"><p class="text-gray-700"><strong>Explanation:</strong> ${data.explanation}</p></div>`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
resultContainer.innerHTML = `
|
|
373
|
+
<div class="${bgColor} border-l-4 ${isCorrect ? 'border-green-500' : 'border-red-500'} p-4 mb-4">
|
|
374
|
+
<div class="flex items-center">
|
|
375
|
+
<span class="text-2xl mr-3">${icon}</span>
|
|
376
|
+
<div>
|
|
377
|
+
<p class="font-bold ${textColor} text-lg">${message}</p>
|
|
378
|
+
<p class="text-gray-600 mt-1">Your answer: <span class="font-semibold">${data.submitted_answer}</span></p>
|
|
379
|
+
${!isCorrect && !isTextDiff ? `<p class="text-gray-600 mt-1">Correct answer: <span class="font-semibold">${data.correct_answer}</span></p>` : ''}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
${explanationHtml}
|
|
384
|
+
<div class="mt-4 text-sm text-gray-500 text-center">Press Enter to continue</div>
|
|
385
|
+
`;
|
|
386
|
+
|
|
387
|
+
// Focus on document to capture Enter key
|
|
388
|
+
document.activeElement.blur();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Reset quiz to initial state
|
|
392
|
+
function resetQuiz() {
|
|
393
|
+
quizView.classList.add('hidden');
|
|
394
|
+
setupView.classList.remove('hidden');
|
|
395
|
+
|
|
396
|
+
// Uncheck all checkboxes
|
|
397
|
+
document.querySelectorAll('.category-checkbox, .sidebar-category-checkbox').forEach(cb => {
|
|
398
|
+
cb.checked = false;
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
selectedCategories = [];
|
|
402
|
+
currentQuestion = null;
|
|
403
|
+
questionNumber = 0;
|
|
404
|
+
showingResult = false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Allow Enter key to submit answer or go to next question
|
|
408
|
+
document.addEventListener('keypress', (e) => {
|
|
409
|
+
if (e.key === 'Enter' && !setupView.classList.contains('hidden')) {
|
|
410
|
+
// Don't handle Enter on setup view
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (e.key === 'Enter' && !quizView.classList.contains('hidden')) {
|
|
414
|
+
submitBtn.click();
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
</script>
|
|
418
|
+
</body>
|
|
419
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from random import randint
|
|
2
|
+
|
|
3
|
+
from ezquiz import APIGame, Q
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_ints():
|
|
7
|
+
return randint(1, 10), randint(1, 10)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ask_addition(t):
|
|
11
|
+
a, b = t
|
|
12
|
+
return f"what is {a} + {b}?"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def correct_addition(t):
|
|
16
|
+
return t[0] + t[1]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
AddQ = Q[tuple[int, int]](
|
|
20
|
+
get_seed=get_ints,
|
|
21
|
+
ask=ask_addition,
|
|
22
|
+
correct=correct_addition,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def ask_mult(t):
|
|
27
|
+
a, b = t
|
|
28
|
+
return f"what is {a} * {b}?"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def correct_mult(t):
|
|
32
|
+
a, b = t
|
|
33
|
+
return a * b
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
MultQ = Q[tuple[int, int]](
|
|
37
|
+
get_seed=get_ints,
|
|
38
|
+
ask=ask_mult,
|
|
39
|
+
correct=correct_mult,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
game = APIGame(
|
|
43
|
+
"Basic Math Test",
|
|
44
|
+
{
|
|
45
|
+
"addition": AddQ,
|
|
46
|
+
"multiplication": MultQ,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
game.start()
|
ezquiz-0.2.0/uv.lock
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 2
|
|
3
|
+
requires-python = ">=3.14"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "annotated-doc"
|
|
7
|
+
version = "0.0.4"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "annotated-types"
|
|
16
|
+
version = "0.7.0"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "anyio"
|
|
25
|
+
version = "4.12.1"
|
|
26
|
+
source = { registry = "https://pypi.org/simple" }
|
|
27
|
+
dependencies = [
|
|
28
|
+
{ name = "idna" },
|
|
29
|
+
]
|
|
30
|
+
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
|
31
|
+
wheels = [
|
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "click"
|
|
37
|
+
version = "8.3.1"
|
|
38
|
+
source = { registry = "https://pypi.org/simple" }
|
|
39
|
+
dependencies = [
|
|
40
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
41
|
+
]
|
|
42
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
|
43
|
+
wheels = [
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "colorama"
|
|
49
|
+
version = "0.4.6"
|
|
50
|
+
source = { registry = "https://pypi.org/simple" }
|
|
51
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
52
|
+
wheels = [
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[[package]]
|
|
57
|
+
name = "fastapi"
|
|
58
|
+
version = "0.128.0"
|
|
59
|
+
source = { registry = "https://pypi.org/simple" }
|
|
60
|
+
dependencies = [
|
|
61
|
+
{ name = "annotated-doc" },
|
|
62
|
+
{ name = "pydantic" },
|
|
63
|
+
{ name = "starlette" },
|
|
64
|
+
{ name = "typing-extensions" },
|
|
65
|
+
]
|
|
66
|
+
sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
|
|
67
|
+
wheels = [
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[[package]]
|
|
72
|
+
name = "h11"
|
|
73
|
+
version = "0.16.0"
|
|
74
|
+
source = { registry = "https://pypi.org/simple" }
|
|
75
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
76
|
+
wheels = [
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "idna"
|
|
82
|
+
version = "3.11"
|
|
83
|
+
source = { registry = "https://pypi.org/simple" }
|
|
84
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
|
85
|
+
wheels = [
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
[[package]]
|
|
90
|
+
name = "jinja2"
|
|
91
|
+
version = "3.1.6"
|
|
92
|
+
source = { registry = "https://pypi.org/simple" }
|
|
93
|
+
dependencies = [
|
|
94
|
+
{ name = "markupsafe" },
|
|
95
|
+
]
|
|
96
|
+
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
|
97
|
+
wheels = [
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
[[package]]
|
|
102
|
+
name = "markupsafe"
|
|
103
|
+
version = "3.0.3"
|
|
104
|
+
source = { registry = "https://pypi.org/simple" }
|
|
105
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
|
106
|
+
wheels = [
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
[[package]]
|
|
132
|
+
name = "pydantic"
|
|
133
|
+
version = "2.12.5"
|
|
134
|
+
source = { registry = "https://pypi.org/simple" }
|
|
135
|
+
dependencies = [
|
|
136
|
+
{ name = "annotated-types" },
|
|
137
|
+
{ name = "pydantic-core" },
|
|
138
|
+
{ name = "typing-extensions" },
|
|
139
|
+
{ name = "typing-inspection" },
|
|
140
|
+
]
|
|
141
|
+
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
|
142
|
+
wheels = [
|
|
143
|
+
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
[[package]]
|
|
147
|
+
name = "pydantic-core"
|
|
148
|
+
version = "2.41.5"
|
|
149
|
+
source = { registry = "https://pypi.org/simple" }
|
|
150
|
+
dependencies = [
|
|
151
|
+
{ name = "typing-extensions" },
|
|
152
|
+
]
|
|
153
|
+
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
|
154
|
+
wheels = [
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
|
158
|
+
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
|
159
|
+
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
[[package]]
|
|
186
|
+
name = "ezquiz"
|
|
187
|
+
version = "0.2.0"
|
|
188
|
+
source = { editable = "." }
|
|
189
|
+
dependencies = [
|
|
190
|
+
{ name = "fastapi" },
|
|
191
|
+
{ name = "jinja2" },
|
|
192
|
+
{ name = "uvicorn" },
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
[package.metadata]
|
|
196
|
+
requires-dist = [
|
|
197
|
+
{ name = "fastapi", specifier = ">=0.128.0" },
|
|
198
|
+
{ name = "jinja2", specifier = ">=3.1.6" },
|
|
199
|
+
{ name = "uvicorn", specifier = ">=0.40.0" },
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
[[package]]
|
|
203
|
+
name = "starlette"
|
|
204
|
+
version = "0.50.0"
|
|
205
|
+
source = { registry = "https://pypi.org/simple" }
|
|
206
|
+
dependencies = [
|
|
207
|
+
{ name = "anyio" },
|
|
208
|
+
]
|
|
209
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
|
|
210
|
+
wheels = [
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
[[package]]
|
|
215
|
+
name = "typing-extensions"
|
|
216
|
+
version = "4.15.0"
|
|
217
|
+
source = { registry = "https://pypi.org/simple" }
|
|
218
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
219
|
+
wheels = [
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
[[package]]
|
|
224
|
+
name = "typing-inspection"
|
|
225
|
+
version = "0.4.2"
|
|
226
|
+
source = { registry = "https://pypi.org/simple" }
|
|
227
|
+
dependencies = [
|
|
228
|
+
{ name = "typing-extensions" },
|
|
229
|
+
]
|
|
230
|
+
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
|
231
|
+
wheels = [
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
[[package]]
|
|
236
|
+
name = "uvicorn"
|
|
237
|
+
version = "0.40.0"
|
|
238
|
+
source = { registry = "https://pypi.org/simple" }
|
|
239
|
+
dependencies = [
|
|
240
|
+
{ name = "click" },
|
|
241
|
+
{ name = "h11" },
|
|
242
|
+
]
|
|
243
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
|
|
244
|
+
wheels = [
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
|
|
246
|
+
]
|