Coverage for src / competitive_verifier / models / problem.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-05 16:00 +0000

1import hashlib 

2import pathlib 

3from abc import ABC, abstractmethod 

4from collections.abc import Iterable 

5from typing import NamedTuple, Optional, cast 

6 

7from competitive_verifier import config 

8 

9 

10class TestCaseFile(NamedTuple): 

11 name: str 

12 input_path: pathlib.Path 

13 output_path: pathlib.Path 

14 

15 

16class TestCaseData(NamedTuple): 

17 name: str 

18 input_data: bytes 

19 output_data: bytes 

20 

21 

22class TestCaseProvider(ABC): 

23 @abstractmethod 

24 def download_system_cases(self) -> Iterable[TestCaseData] | bool: ... 

25 

26 @abstractmethod 

27 def iter_system_cases(self) -> Iterable[TestCaseFile]: ... 

28 

29 @property 

30 def checker(self) -> pathlib.Path | None: 

31 return None 

32 

33 

34class Problem(TestCaseProvider): 

35 def __repr__(self) -> str: 

36 return f"{self.__class__.__name__}.from_url({self.url!r})" 

37 

38 def __hash__(self) -> int: # pragma: no cover 

39 return hash(self.url) ^ hash(type(self)) 

40 

41 def __eq__(self, value: object) -> bool: 

42 if type(self) is not type(value): 

43 return False 

44 return self.url == cast("Problem", value).url 

45 

46 @property 

47 @abstractmethod 

48 def url(self) -> str: ... 

49 

50 @classmethod 

51 @abstractmethod 

52 def from_url(cls, url: str) -> Optional["Problem"]: ... 

53 

54 @property 

55 def hash_id(self): 

56 return hashlib.md5(self.url.encode(), usedforsecurity=False).hexdigest() 

57 

58 @property 

59 def problem_directory(self): 

60 return config.get_problem_cache_dir() / self.hash_id 

61 

62 @property 

63 def test_directory(self): 

64 return self.problem_directory / "test"