Coverage for src / competitive_verifier / documents / render_data.py: 100%

56 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-04-26 12:38 +0900

1import datetime 

2import enum 

3from collections.abc import Sequence 

4from typing import Annotated, Any 

5 

6from pydantic import BaseModel, ConfigDict, PlainSerializer, model_validator 

7from pydantic.alias_generators import to_camel 

8 

9from competitive_verifier.models import ForcePosixPath, SortedPathList, TestcaseResult 

10 

11 

12class FileType(enum.Enum): 

13 LIBRARY = enum.auto() 

14 TEST = enum.auto() 

15 

16 

17class StatusIcon(str, enum.Enum): 

18 def __new__(cls, file_type: FileType, result: str, _: object) -> "StatusIcon": 

19 value = f"{file_type.name}_{result}" 

20 member = str.__new__(cls, value) 

21 member._value_ = value 

22 return member 

23 

24 def __init__(self, file_type: FileType, result: str, is_success: bool) -> None: # noqa: FBT001 

25 super().__init__() 

26 self.file_type = file_type 

27 self.result = result 

28 self.is_success = is_success 

29 

30 LIBRARY_ALL_AC = (FileType.LIBRARY, "ALL_AC", True) 

31 LIBRARY_PARTIAL_AC = (FileType.LIBRARY, "PARTIAL_AC", True) 

32 LIBRARY_SOME_WA = (FileType.LIBRARY, "SOME_WA", False) 

33 LIBRARY_ALL_WA = (FileType.LIBRARY, "ALL_WA", False) 

34 LIBRARY_NO_TESTS = (FileType.LIBRARY, "NO_TESTS", True) 

35 TEST_ACCEPTED = (FileType.TEST, "ACCEPTED", True) 

36 TEST_WRONG_ANSWER = (FileType.TEST, "WRONG_ANSWER", False) 

37 TEST_WAITING_JUDGE = (FileType.TEST, "WAITING_JUDGE", True) 

38 

39 @property 

40 def is_failed(self) -> bool: 

41 return not self.is_success 

42 

43 @property 

44 def is_test(self) -> bool: 

45 return self.file_type == FileType.TEST 

46 

47 

48class RenderBaseModel(BaseModel): 

49 model_config = ConfigDict( 

50 alias_generator=to_camel, 

51 populate_by_name=True, 

52 extra="allow", 

53 ) 

54 

55 

56class RenderLink(RenderBaseModel): 

57 path: ForcePosixPath 

58 filename: str 

59 icon: StatusIcon 

60 title: str | None = None 

61 

62 @model_validator(mode="after") 

63 def validate_title(self: "RenderLink") -> "RenderLink": 

64 if self.path.as_posix() == self.title: 

65 self.title = None 

66 return self 

67 

68 

69class Dependency(RenderBaseModel): 

70 type: str 

71 files: list[RenderLink] 

72 

73 

74class EmbeddedCode(RenderBaseModel): 

75 name: str 

76 code: str 

77 

78 

79class EnvTestcaseResult(RenderBaseModel, TestcaseResult): 

80 environment: str | None 

81 

82 

83class CategorizedIndex(RenderBaseModel): 

84 name: str 

85 pages: list[RenderLink] 

86 

87 

88class IndexFiles(RenderBaseModel): 

89 type: str 

90 categories: list[CategorizedIndex] 

91 

92 

93class PageRenderData(RenderBaseModel): 

94 path: ForcePosixPath 

95 path_extension: str 

96 title: str | None 

97 embedded: list[EmbeddedCode] 

98 

99 timestamp: Annotated[ 

100 datetime.datetime, 

101 PlainSerializer(lambda x: str(x), return_type=str, when_used="json"), 

102 ] 

103 attributes: dict[str, Any] 

104 testcases: list[EnvTestcaseResult] | None = None 

105 

106 is_failed: bool 

107 is_verification_file: bool 

108 verification_status: StatusIcon 

109 

110 depends_on: SortedPathList 

111 required_by: SortedPathList 

112 verified_with: SortedPathList 

113 

114 document_path: ForcePosixPath | None = None 

115 dependencies: list[Dependency] 

116 

117 

118class CodePageData(PageRenderData): 

119 document_content: str | None 

120 

121 

122class MultiCodePageData(RenderBaseModel): 

123 path: ForcePosixPath 

124 verification_status: StatusIcon 

125 is_failed: bool 

126 codes: Sequence[CodePageData] 

127 dependencies: list[Dependency] 

128 

129 

130class IndexRenderData(RenderBaseModel): 

131 top: list[IndexFiles]