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