from collections.abc import Sequence, Iterable, Mapping from typing import Any import inspect from abc import ABC, abstractmethod import weakref from PIL import ImageColor from colour import Color PASSED: str = "#1AFE49" FAILED: str = "#FF124F" def hex_to_rgba(color: str) -> tuple: return tuple([e/255 for e in ImageColor.getcolor(color, "RGBA")]) def gradient(color1: str, color2: str, num: int) -> list[tuple]: c1, c2 = Color(color1), Color(color2) colors = list(c2.range_to(c1, num)) colors = [hex_to_rgba(str(c)) for c in colors] return colors class BaseGrading(Mapping, ABC): __instances: list[Mapping] = list() def __init__(self, schema: dict[str, int | float], name=None, alt_name=None): all_str = all(isinstance(k, str) for k in schema.keys()) assert all_str or all(isinstance(k, int) for k in schema.keys()), "Keys must be all of type (str, int)" assert all(isinstance(v, float) for v in schema.values()), "All values must be floats in range(0,1)" assert all(v <= 1 and v >= 0 for v in schema.values()), "All values must be floats in range(0,1)" if all_str: self.schema = dict() for k, v in schema.items(): self.schema[k.title()] = v else: self.schema = schema self.__class__.__instances.append(weakref.proxy(self)) self.name = name self.alt_name = alt_name def __getitem__(self, index): if index >= len(self): raise IndexError return self.schema[index] def __len__(self) -> int: return len(self.schema) def __contains__(self, item: int | str | float) -> bool: if isinstance(item, (int, str)): if isinstance(item, str): item = item.title() return item in self.schema if isinstance(item, float): return item <= 1 and item >= 0 return False def __iter__(self) -> Iterable: yield from self.schema def __reversed__(self) -> Iterable: yield from reversed(self.schema) def __str__(self) -> str: return self.name def __repr__(self) -> str: return f"<{self.name}: ({str(self.schema)})>" def __eq__(self, other) -> bool: if other == self: return True if isinstance(other, BaseEval): return self.schema == other.schema return NotImplemented def __ne__(self, other) -> bool: return not self.__eq__(other) def get(self, key: int | str) -> float | None: if isinstance(key, str): key = key.title() if key in self: return self.schema[key] return None def keys(self) -> tuple: return list(self.schema.keys()) def values(self) -> tuple: return list(self.schema.values()) def items(self) -> list[tuple]: return list(self.schema.items()) @abstractmethod def has_passed(self, value: int | float, max: int | float) -> bool: pass @abstractmethod def get_grade(self, value: int | float, max: int | float) -> str | int: pass @abstractmethod def get_grade_color(self, value: int | float, max: int | float) -> tuple: pass @classmethod def get_instances(cls): yield from cls.__instances @classmethod def get_instance(cls, name: str): for instance in cls.__instances: if instance.alt_name == name: return instance get_gradings = lambda: BaseGrading.get_instances() get_grader = lambda name: BaseGrading.get_instance(name) class StdPercentRule(BaseGrading): def has_passed(self, value: int | float, max: int | float) -> bool: return value >= max * self.schema["Passed"] def get_grade(self, value: int | float, max: int | float) -> str: return "Passed" if self.has_passed(value, max) else "Not Passed" def get_grade_color(self, value: int | float, max: int | float) -> tuple: if self.has_passed(value, max): return hex_to_rgba(PASSED) return hex_to_rgba(FAILED) class StdGermanGrading(BaseGrading): def has_passed(self, value: int | float, max: int | float) -> bool: return value/max >= 0.45 def search_grade(self, value: float) -> int: if value <= 0: return min(self.schema.keys()) searched = max(self.schema.keys()) found = False while not found: if self.schema[searched] <= value: found = True else: searched -= 1 return searched def get_grade(self, value: int | float, max: int | float) -> int: return self.search_grade(value/max) def get_grade_color(self, value: float, max: int | float) -> tuple: grade = self.get_grade(value, max) colors = gradient(PASSED, FAILED, len(self.schema)) return colors[grade] # Definitions Std30PercentRule = StdPercentRule({ "pAssed": 0.3, "Failed": 0.0 }, "Std30PercentRule", "30 Percent") Std50PercentRule = StdPercentRule({ "Passed": 0.5, "Failed": 0.0 }, "Std50PercentRule", "50 Percent") StdGermanGradingMiddleSchool = StdGermanGrading({ 1: 0.96, 2: 0.80, 3: 0.60, 4: 0.45, 5: 0.16, 6: 0.00 }, "StdGermanGradingMiddleSchool", "Secondary School") StdGermanGradingHighSchool = StdGermanGrading({ 15: 0.95, 14: 0.90, 13: 0.85, 12: 0.80, 11: 0.75, 10: 0.70, 9: 0.65, 8: 0.60, 7: 0.55, 6: 0.50, 5: 0.45, 4: 0.40, 3: 0.33, 2: 0.27, 1: 0.20, 0: 0.00 }, "StdGermanGradingHighSchool", "Oberstufe") #print(StdGermanGradingHighSchool.get_grade(0.0, 24))