QuizGenerator 0.4.2__py3-none-any.whl
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.
- QuizGenerator/README.md +5 -0
- QuizGenerator/__init__.py +27 -0
- QuizGenerator/__main__.py +7 -0
- QuizGenerator/canvas/__init__.py +13 -0
- QuizGenerator/canvas/canvas_interface.py +627 -0
- QuizGenerator/canvas/classes.py +235 -0
- QuizGenerator/constants.py +149 -0
- QuizGenerator/contentast.py +1955 -0
- QuizGenerator/generate.py +253 -0
- QuizGenerator/logging.yaml +55 -0
- QuizGenerator/misc.py +579 -0
- QuizGenerator/mixins.py +548 -0
- QuizGenerator/performance.py +202 -0
- QuizGenerator/premade_questions/__init__.py +0 -0
- QuizGenerator/premade_questions/basic.py +103 -0
- QuizGenerator/premade_questions/cst334/__init__.py +1 -0
- QuizGenerator/premade_questions/cst334/languages.py +391 -0
- QuizGenerator/premade_questions/cst334/math_questions.py +297 -0
- QuizGenerator/premade_questions/cst334/memory_questions.py +1400 -0
- QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +572 -0
- QuizGenerator/premade_questions/cst334/persistence_questions.py +451 -0
- QuizGenerator/premade_questions/cst334/process.py +648 -0
- QuizGenerator/premade_questions/cst463/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +3 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +369 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +305 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +650 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +73 -0
- QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +2 -0
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +631 -0
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +534 -0
- QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/models/attention.py +192 -0
- QuizGenerator/premade_questions/cst463/models/cnns.py +186 -0
- QuizGenerator/premade_questions/cst463/models/matrices.py +24 -0
- QuizGenerator/premade_questions/cst463/models/rnns.py +202 -0
- QuizGenerator/premade_questions/cst463/models/text.py +203 -0
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +227 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +1314 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +936 -0
- QuizGenerator/qrcode_generator.py +293 -0
- QuizGenerator/question.py +715 -0
- QuizGenerator/quiz.py +467 -0
- QuizGenerator/regenerate.py +472 -0
- QuizGenerator/typst_utils.py +113 -0
- quizgenerator-0.4.2.dist-info/METADATA +265 -0
- quizgenerator-0.4.2.dist-info/RECORD +52 -0
- quizgenerator-0.4.2.dist-info/WHEEL +4 -0
- quizgenerator-0.4.2.dist-info/entry_points.txt +3 -0
- quizgenerator-0.4.2.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import enum
|
|
5
|
+
import logging
|
|
6
|
+
import dataclasses
|
|
7
|
+
import functools
|
|
8
|
+
import io
|
|
9
|
+
import os
|
|
10
|
+
import urllib.request
|
|
11
|
+
from typing import Optional, List, Dict
|
|
12
|
+
|
|
13
|
+
import canvasapi.canvas
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LMSWrapper():
|
|
20
|
+
def __init__(self, _inner):
|
|
21
|
+
self._inner = _inner
|
|
22
|
+
|
|
23
|
+
def __getattr__(self, name):
|
|
24
|
+
try:
|
|
25
|
+
# Try to get the attribute from the inner instance
|
|
26
|
+
return getattr(self._inner, name)
|
|
27
|
+
except AttributeError:
|
|
28
|
+
# Handle the case where the inner instance also doesn't have the attribute
|
|
29
|
+
print(f"Warning: '{name}' not found in either wrapper or inner class")
|
|
30
|
+
# You can raise the error again, return None, or handle it however you want
|
|
31
|
+
return lambda *args, **kwargs: None # Returns a no-op function for method calls
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclasses.dataclass
|
|
35
|
+
class Student(LMSWrapper):
|
|
36
|
+
name : str
|
|
37
|
+
user_id : int
|
|
38
|
+
_inner : canvasapi.canvas.User
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Submission:
|
|
42
|
+
|
|
43
|
+
class Status(enum.Enum):
|
|
44
|
+
MISSING = "unsubmitted"
|
|
45
|
+
UNGRADED = ("submitted", "pending_review")
|
|
46
|
+
GRADED = "graded"
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_string(cls, status_string, current_score):
|
|
50
|
+
for status in cls:
|
|
51
|
+
if status is not cls.MISSING and current_score is None:
|
|
52
|
+
return cls.UNGRADED
|
|
53
|
+
if isinstance(status.value, tuple):
|
|
54
|
+
if status_string in status.value:
|
|
55
|
+
return status
|
|
56
|
+
elif status_string == status.value:
|
|
57
|
+
return status
|
|
58
|
+
return cls.MISSING # Default
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
student : Student = None,
|
|
65
|
+
status : Submission.Status = Status.UNGRADED,
|
|
66
|
+
**kwargs
|
|
67
|
+
):
|
|
68
|
+
self._student: Optional[Student] = student
|
|
69
|
+
self.status = status
|
|
70
|
+
self.input_files = None
|
|
71
|
+
self.feedback : Optional[Feedback] = None
|
|
72
|
+
self.extra_info = {}
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def student(self):
|
|
76
|
+
return self._student
|
|
77
|
+
|
|
78
|
+
@student.setter
|
|
79
|
+
def student(self, student):
|
|
80
|
+
self._student = student
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
try:
|
|
84
|
+
return f"Submission({self.student.name} : {self.feedback})"
|
|
85
|
+
except AttributeError:
|
|
86
|
+
return f"Submission({self.student} : {self.feedback})"
|
|
87
|
+
|
|
88
|
+
def set_extra(self, extras_dict: Dict):
|
|
89
|
+
self.extra_info.update(extras_dict)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class FileSubmission(Submission):
|
|
93
|
+
"""Base class for submissions that contain files (e.g., programming assignments)"""
|
|
94
|
+
def __init__(self, *args, **kwargs):
|
|
95
|
+
super().__init__(*args, **kwargs)
|
|
96
|
+
self._files = None
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def files(self):
|
|
100
|
+
return self._files
|
|
101
|
+
|
|
102
|
+
@files.setter
|
|
103
|
+
def files(self, files):
|
|
104
|
+
self._files = files
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FileSubmission__Canvas(FileSubmission):
|
|
108
|
+
"""Canvas-specific file submission with attachment downloading"""
|
|
109
|
+
def __init__(self, *args, attachments : Optional[List], **kwargs):
|
|
110
|
+
super().__init__(*args, **kwargs)
|
|
111
|
+
self._attachments = attachments
|
|
112
|
+
self.submission_index = kwargs.get("submission_index", None)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def files(self):
|
|
116
|
+
# Check if we have already downloaded the files locally and return if we have
|
|
117
|
+
if self._files is not None:
|
|
118
|
+
return self._files
|
|
119
|
+
|
|
120
|
+
# If we haven't downloaded the files yet, check if we have attachments and can download them
|
|
121
|
+
if self._attachments is not None:
|
|
122
|
+
self._files = []
|
|
123
|
+
for attachment in self._attachments:
|
|
124
|
+
|
|
125
|
+
# Generate a local file name with a number of options
|
|
126
|
+
# local_file_name = f"{self.student.name.replace(' ', '-')}_{self.student.user_id}_{attachment['filename']}"
|
|
127
|
+
local_file_name = f"{attachment['filename']}"
|
|
128
|
+
with urllib.request.urlopen(attachment['url']) as response:
|
|
129
|
+
buffer = io.BytesIO(response.read())
|
|
130
|
+
buffer.name = local_file_name
|
|
131
|
+
self._files.append(buffer)
|
|
132
|
+
|
|
133
|
+
return self._files
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TextSubmission(Submission):
|
|
137
|
+
"""Submission containing text content (e.g., journal entries, essays)"""
|
|
138
|
+
def __init__(self, *args, submission_text=None, **kwargs):
|
|
139
|
+
super().__init__(*args, **kwargs)
|
|
140
|
+
self.submission_text = submission_text or ""
|
|
141
|
+
|
|
142
|
+
def get_text(self):
|
|
143
|
+
"""Get the submission text content"""
|
|
144
|
+
return self.submission_text
|
|
145
|
+
|
|
146
|
+
def get_word_count(self):
|
|
147
|
+
"""Get word count of the submission"""
|
|
148
|
+
return len(self.submission_text.split()) if self.submission_text else 0
|
|
149
|
+
|
|
150
|
+
def get_character_count(self, include_spaces=True):
|
|
151
|
+
"""Get character count of the submission"""
|
|
152
|
+
if not self.submission_text:
|
|
153
|
+
return 0
|
|
154
|
+
return len(self.submission_text) if include_spaces else len(self.submission_text.replace(' ', ''))
|
|
155
|
+
|
|
156
|
+
def get_paragraph_count(self):
|
|
157
|
+
"""Get paragraph count (separated by double newlines)"""
|
|
158
|
+
if not self.submission_text:
|
|
159
|
+
return 0
|
|
160
|
+
paragraphs = [p.strip() for p in self.submission_text.split('\n\n') if p.strip()]
|
|
161
|
+
return len(paragraphs)
|
|
162
|
+
|
|
163
|
+
def __str__(self):
|
|
164
|
+
try:
|
|
165
|
+
word_count = self.get_word_count()
|
|
166
|
+
return f"TextSubmission({self.student.name} : {word_count} words : {self.feedback})"
|
|
167
|
+
except AttributeError:
|
|
168
|
+
return f"TextSubmission({self.student} : {self.get_word_count()} words : {self.feedback})"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TextSubmission__Canvas(TextSubmission):
|
|
172
|
+
"""Canvas-specific text submission"""
|
|
173
|
+
def __init__(self, *args, canvas_submission_data=None, **kwargs):
|
|
174
|
+
submission_text = ""
|
|
175
|
+
if canvas_submission_data and hasattr(canvas_submission_data, 'body') and canvas_submission_data.body:
|
|
176
|
+
submission_text = canvas_submission_data.body
|
|
177
|
+
|
|
178
|
+
super().__init__(*args, submission_text=submission_text, **kwargs)
|
|
179
|
+
self.canvas_submission_data = canvas_submission_data
|
|
180
|
+
self.submission_index = kwargs.get("submission_index", None)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class QuizSubmission(Submission):
|
|
184
|
+
"""Submission containing quiz responses and question metadata"""
|
|
185
|
+
def __init__(self, *args, quiz_submission_data=None, student_responses=None, quiz_questions=None, **kwargs):
|
|
186
|
+
super().__init__(*args, **kwargs)
|
|
187
|
+
self.quiz_submission_data = quiz_submission_data
|
|
188
|
+
self.responses = student_responses or {} # Dict mapping question_id -> response
|
|
189
|
+
self.questions = quiz_questions or {} # Dict mapping question_id -> question metadata
|
|
190
|
+
|
|
191
|
+
def get_response(self, question_id: int):
|
|
192
|
+
"""Get student's response to a specific question"""
|
|
193
|
+
return self.responses.get(question_id)
|
|
194
|
+
|
|
195
|
+
def get_question(self, question_id: int):
|
|
196
|
+
"""Get question metadata for a specific question"""
|
|
197
|
+
return self.questions.get(question_id)
|
|
198
|
+
|
|
199
|
+
def __str__(self):
|
|
200
|
+
try:
|
|
201
|
+
response_count = len(self.responses)
|
|
202
|
+
return f"QuizSubmission({self.student.name} : {response_count} responses : {self.feedback})"
|
|
203
|
+
except AttributeError:
|
|
204
|
+
return f"QuizSubmission({self.student} : {len(self.responses)} responses : {self.feedback})"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Maintain backward compatibility
|
|
208
|
+
Submission__Canvas = FileSubmission__Canvas
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@functools.total_ordering
|
|
212
|
+
@dataclasses.dataclass
|
|
213
|
+
class Feedback:
|
|
214
|
+
percentage_score: Optional[float] = None
|
|
215
|
+
comments: str = ""
|
|
216
|
+
attachments: List[io.BytesIO] = dataclasses.field(default_factory=list)
|
|
217
|
+
|
|
218
|
+
def __str__(self):
|
|
219
|
+
short_comment = self.comments[:10].replace('\n', '\\n')
|
|
220
|
+
ellipsis = '...' if len(self.comments) > 10 else ''
|
|
221
|
+
return f"Feedback({self.percentage_score:.4g}%, {short_comment}{ellipsis})"
|
|
222
|
+
|
|
223
|
+
def __eq__(self, other):
|
|
224
|
+
if not isinstance(other, Feedback):
|
|
225
|
+
return NotImplemented
|
|
226
|
+
return self.percentage_score == other.percentage_score
|
|
227
|
+
|
|
228
|
+
def __lt__(self, other):
|
|
229
|
+
if not isinstance(other, Feedback):
|
|
230
|
+
return NotImplemented
|
|
231
|
+
if self.percentage_score is None:
|
|
232
|
+
return False # None is treated as greater than any other value
|
|
233
|
+
if other.percentage_score is None:
|
|
234
|
+
return True
|
|
235
|
+
return self.percentage_score < other.percentage_score
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!env python
|
|
2
|
+
"""
|
|
3
|
+
Common constants used across question types.
|
|
4
|
+
Centralizing these values makes it easier to maintain consistency
|
|
5
|
+
and adjust ranges globally.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Bit-related constants
|
|
9
|
+
class BitRanges:
|
|
10
|
+
DEFAULT_MIN_BITS = 3
|
|
11
|
+
DEFAULT_MAX_BITS = 16
|
|
12
|
+
|
|
13
|
+
# Memory addressing specific (using standardized names)
|
|
14
|
+
DEFAULT_MIN_BITS_VA = 5
|
|
15
|
+
DEFAULT_MAX_BITS_VA = 10
|
|
16
|
+
DEFAULT_MIN_BITS_OFFSET = 3
|
|
17
|
+
DEFAULT_MAX_BITS_OFFSET = 8
|
|
18
|
+
DEFAULT_MIN_BITS_VPN = 3
|
|
19
|
+
DEFAULT_MAX_BITS_VPN = 8
|
|
20
|
+
DEFAULT_MIN_BITS_PFN = 3
|
|
21
|
+
DEFAULT_MAX_BITS_PFN = 16
|
|
22
|
+
|
|
23
|
+
# Backward compatibility - deprecated, use standardized names above
|
|
24
|
+
DEFAULT_MIN_VA_BITS = DEFAULT_MIN_BITS_VA
|
|
25
|
+
DEFAULT_MAX_VA_BITS = DEFAULT_MAX_BITS_VA
|
|
26
|
+
DEFAULT_MIN_OFFSET_BITS = DEFAULT_MIN_BITS_OFFSET
|
|
27
|
+
DEFAULT_MAX_OFFSET_BITS = DEFAULT_MAX_BITS_OFFSET
|
|
28
|
+
DEFAULT_MIN_VPN_BITS = DEFAULT_MIN_BITS_VPN
|
|
29
|
+
DEFAULT_MAX_VPN_BITS = DEFAULT_MAX_BITS_VPN
|
|
30
|
+
DEFAULT_MIN_PFN_BITS = DEFAULT_MIN_BITS_PFN
|
|
31
|
+
DEFAULT_MAX_PFN_BITS = DEFAULT_MAX_BITS_PFN
|
|
32
|
+
|
|
33
|
+
# Base and bounds
|
|
34
|
+
DEFAULT_MAX_ADDRESS_BITS = 32
|
|
35
|
+
DEFAULT_MIN_BOUNDS_BITS = 5
|
|
36
|
+
DEFAULT_MAX_BOUNDS_BITS = 16
|
|
37
|
+
|
|
38
|
+
# Job/Process constants
|
|
39
|
+
class ProcessRanges:
|
|
40
|
+
DEFAULT_MIN_JOBS = 2
|
|
41
|
+
DEFAULT_MAX_JOBS = 5
|
|
42
|
+
DEFAULT_MIN_DURATION = 2
|
|
43
|
+
DEFAULT_MAX_DURATION = 10
|
|
44
|
+
DEFAULT_MIN_ARRIVAL_TIME = 0
|
|
45
|
+
DEFAULT_MAX_ARRIVAL_TIME = 20
|
|
46
|
+
|
|
47
|
+
# Cache/Memory constants
|
|
48
|
+
class CacheRanges:
|
|
49
|
+
DEFAULT_MIN_CACHE_SIZE = 2
|
|
50
|
+
DEFAULT_MAX_CACHE_SIZE = 8
|
|
51
|
+
DEFAULT_MIN_ELEMENTS = 3
|
|
52
|
+
DEFAULT_MAX_ELEMENTS = 10
|
|
53
|
+
DEFAULT_MIN_REQUESTS = 5
|
|
54
|
+
DEFAULT_MAX_REQUESTS = 20
|
|
55
|
+
|
|
56
|
+
# Disk/IO constants
|
|
57
|
+
class IOConstants:
|
|
58
|
+
DEFAULT_MIN_RPM = 3600
|
|
59
|
+
DEFAULT_MAX_RPM = 15000
|
|
60
|
+
DEFAULT_MIN_SEEK_DELAY = 3.0
|
|
61
|
+
DEFAULT_MAX_SEEK_DELAY = 20.0
|
|
62
|
+
DEFAULT_MIN_TRANSFER_RATE = 50
|
|
63
|
+
DEFAULT_MAX_TRANSFER_RATE = 300
|
|
64
|
+
|
|
65
|
+
# Math question constants
|
|
66
|
+
class MathRanges:
|
|
67
|
+
DEFAULT_MIN_MATH_BITS = 3
|
|
68
|
+
DEFAULT_MAX_MATH_BITS = 49
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# =============================================================================
|
|
72
|
+
# Utility Functions
|
|
73
|
+
# =============================================================================
|
|
74
|
+
|
|
75
|
+
def get_bit_range(bit_type: str) -> tuple[int, int]:
|
|
76
|
+
"""
|
|
77
|
+
Get the min/max range for a specific type of bit parameter.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
bit_type: One of 'va', 'offset', 'vpn', 'pfn', 'general'
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Tuple of (min_bits, max_bits)
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: If bit_type is not recognized
|
|
87
|
+
"""
|
|
88
|
+
ranges = {
|
|
89
|
+
'va': (BitRanges.DEFAULT_MIN_BITS_VA, BitRanges.DEFAULT_MAX_BITS_VA),
|
|
90
|
+
'offset': (BitRanges.DEFAULT_MIN_BITS_OFFSET, BitRanges.DEFAULT_MAX_BITS_OFFSET),
|
|
91
|
+
'vpn': (BitRanges.DEFAULT_MIN_BITS_VPN, BitRanges.DEFAULT_MAX_BITS_VPN),
|
|
92
|
+
'pfn': (BitRanges.DEFAULT_MIN_BITS_PFN, BitRanges.DEFAULT_MAX_BITS_PFN),
|
|
93
|
+
'general': (BitRanges.DEFAULT_MIN_BITS, BitRanges.DEFAULT_MAX_BITS),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if bit_type not in ranges:
|
|
97
|
+
raise ValueError(f"Unknown bit_type '{bit_type}'. Valid types: {list(ranges.keys())}")
|
|
98
|
+
|
|
99
|
+
return ranges[bit_type]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_process_range(param_type: str) -> tuple[int, int]:
|
|
103
|
+
"""
|
|
104
|
+
Get the min/max range for job/process parameters.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
param_type: One of 'jobs', 'duration', 'arrival_time'
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Tuple of (min_value, max_value)
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If param_type is not recognized
|
|
114
|
+
"""
|
|
115
|
+
ranges = {
|
|
116
|
+
'jobs': (ProcessRanges.DEFAULT_MIN_JOBS, ProcessRanges.DEFAULT_MAX_JOBS),
|
|
117
|
+
'duration': (ProcessRanges.DEFAULT_MIN_DURATION, ProcessRanges.DEFAULT_MAX_DURATION),
|
|
118
|
+
'arrival_time': (ProcessRanges.DEFAULT_MIN_ARRIVAL_TIME, ProcessRanges.DEFAULT_MAX_ARRIVAL_TIME),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if param_type not in ranges:
|
|
122
|
+
raise ValueError(f"Unknown param_type '{param_type}'. Valid types: {list(ranges.keys())}")
|
|
123
|
+
|
|
124
|
+
return ranges[param_type]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_cache_range(param_type: str) -> tuple[int, int]:
|
|
128
|
+
"""
|
|
129
|
+
Get the min/max range for cache/memory parameters.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
param_type: One of 'cache_size', 'elements', 'requests'
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Tuple of (min_value, max_value)
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If param_type is not recognized
|
|
139
|
+
"""
|
|
140
|
+
ranges = {
|
|
141
|
+
'cache_size': (CacheRanges.DEFAULT_MIN_CACHE_SIZE, CacheRanges.DEFAULT_MAX_CACHE_SIZE),
|
|
142
|
+
'elements': (CacheRanges.DEFAULT_MIN_ELEMENTS, CacheRanges.DEFAULT_MAX_ELEMENTS),
|
|
143
|
+
'requests': (CacheRanges.DEFAULT_MIN_REQUESTS, CacheRanges.DEFAULT_MAX_REQUESTS),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if param_type not in ranges:
|
|
147
|
+
raise ValueError(f"Unknown param_type '{param_type}'. Valid types: {list(ranges.keys())}")
|
|
148
|
+
|
|
149
|
+
return ranges[param_type]
|