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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -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,4 @@
1
+ from ezquiz.apigame import APIGame
2
+ from ezquiz.ezquiz import Prompt, Q
3
+
4
+ __all__ = ["Q", "APIGame", "Prompt"]
@@ -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 === " " ? "&nbsp;" : 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
+ ]