Source code for coursebanks.assessments
"""Shared assessment eligibility rules and legacy YAML migration."""
from .models import Question
from .yaml_data import YamlMapping
[docs]
def assessment_name(value: object) -> str:
"""Validate an assessment name without changing its text.
Parameters
----------
value
User-defined assessment name. It must be a string containing at
least one non-whitespace character.
Returns
-------
name
Original string with case and leading or trailing whitespace intact.
Raises
------
ValueError
If the value is not a string or is empty or whitespace-only.
Notes
-----
Names are not limited to a predefined set. Eligibility comparisons use
exact string equality; validation does not trim or normalize names.
"""
if not isinstance(value, str) or not value.strip():
raise ValueError("assessment names must be nonempty strings")
return value
[docs]
def question_assessments(data: YamlMapping) -> list[str]:
"""Validate assessment eligibility or convert a legacy exam field.
Parameters
----------
data
Question mapping containing an optional assessments list or legacy
exam string. Other fields are ignored by this helper.
Returns
-------
names
New list of unique, nonblank assessment names. An omitted field or
empty list gives unrestricted eligibility. Legacy exam Either also
becomes an empty list; any other valid legacy name becomes a
single-element list.
Raises
------
ValueError
If both eligibility fields are present, assessments is not a list,
a name is invalid, or the list contains duplicate names.
Notes
-----
Name order, case, and whitespace are preserved. Duplicate checks use
exact equality. Either has special meaning only in the legacy exam
field; an assessments entry named Either is an ordinary restriction.
The input mapping and any contained list are not modified.
"""
if "exam" in data:
if "assessments" in data:
raise ValueError("use assessments or legacy exam, not both")
name = assessment_name(data["exam"])
return [] if name == "Either" else [name]
values = data.get("assessments", [])
if not isinstance(values, list):
# Schema diagnostics use ValueError, including malformed YAML types.
raise ValueError("assessments must be a list of nonempty strings") # ruff: ignore[type-check-without-type-error]
names = [assessment_name(value) for value in values]
if len(names) != len(set(names)):
raise ValueError("assessments must contain unique names")
return names
[docs]
def is_eligible(question: Question, assessment: str) -> bool:
"""Check whether a validated question allows a named assessment.
Parameters
----------
question
Validated question whose assessments list restricts eligibility.
An empty list, or an absent key, permits any valid assessment name.
assessment
Nonblank assessment name to compare using exact string equality.
Returns
-------
eligible
True if eligibility is unrestricted or the name occurs in the
assessments list; otherwise False.
Raises
------
ValueError
If the requested assessment name is not a nonblank string.
Notes
-----
This helper validates the requested name, not the question's contents.
It does not consult usage history or check whether the question has
already been selected. Neither input is modified.
""" # noqa: DOC502
name = assessment_name(assessment)
names = question.get("assessments", [])
return not names or name in names