Skip to content

Commit ae5c38e

Browse files
authored
Add mypy types and pathlib (#4)
1 parent 2ebc9e5 commit ae5c38e

File tree

11 files changed

+352
-261
lines changed

11 files changed

+352
-261
lines changed

homework_checker/check_homework.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def main():
3434
# Read the job file.
3535
log.debug('Reading from file "%s"', args.input)
3636
checker = Checker(args.input)
37-
results = checker.check_homework()
37+
results = checker.check_all_homeworks()
3838
md_writer = MdWriter()
3939
md_writer.update(results)
4040
# Write the resulting markdown file.

homework_checker/checker.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Check the homework."""
22

3-
from os import path
4-
53
import logging
64
from datetime import datetime
5+
from typing import Dict
6+
from pathlib import Path
77

88
from . import tools
99
from .schema_manager import SchemaManager
@@ -13,47 +13,51 @@
1313

1414
log = logging.getLogger("GHC")
1515

16+
HomeworkResultDict = Dict[str, Dict[str, tools.CmdResult]]
17+
1618

1719
class Checker:
1820
"""Check homework."""
1921

2022
TESTS_TAG = "tests"
2123

22-
def __init__(self, job_file_path):
24+
def __init__(self: "Checker", job_file_path: Path):
2325
"""Initialize the checker from file."""
2426
self._job_file_path = tools.expand_if_needed(job_file_path)
2527
schema_manager = SchemaManager(self._job_file_path)
2628
self._base_node = schema_manager.validated_yaml
2729
self._checked_code_folder = tools.expand_if_needed(
28-
self._base_node[Tags.FOLDER_TAG]
30+
Path(self._base_node[Tags.FOLDER_TAG])
2931
)
30-
# The results of all tests will be kept here.
31-
self._results = {}
3232

33-
def check_homework(self):
34-
"""Run over all Tasks in all homeworks."""
35-
results = {}
36-
for homework_node in self._base_node[Tags.HOMEWORKS_TAG]:
37-
current_folder = path.join(
38-
self._checked_code_folder, homework_node[Tags.FOLDER_TAG]
33+
def check_homework(self: "Checker", homework_node: dict) -> HomeworkResultDict:
34+
"""Run over all Tasks in a single homework."""
35+
results: HomeworkResultDict = {}
36+
current_folder = Path(self._checked_code_folder, homework_node[Tags.FOLDER_TAG])
37+
if not current_folder.exists():
38+
log.warning("Folder '%s' does not exist. Skiping.", current_folder)
39+
return results
40+
deadline_str = homework_node[Tags.DEADLINE_TAG]
41+
deadline_datetime = datetime.strptime(deadline_str, tools.DATE_PATTERN)
42+
if datetime.now() > deadline_datetime:
43+
results[tools.EXPIRED_TAG] = {}
44+
for task_node in homework_node[Tags.TASKS_TAG]:
45+
task = Task.from_yaml_node(
46+
task_node=task_node,
47+
student_hw_folder=current_folder,
48+
job_file=self._job_file_path,
3949
)
40-
if not path.exists(current_folder):
41-
log.warning("Folder '%s' does not exist. Skiping.", current_folder)
50+
if not task:
4251
continue
43-
hw_name = homework_node[Tags.NAME_TAG]
44-
results[hw_name] = {}
45-
deadline_str = homework_node[Tags.DEADLINE_TAG]
46-
deadline_datetime = datetime.strptime(deadline_str, tools.DATE_PATTERN)
47-
if datetime.now() > deadline_datetime:
48-
results[hw_name][tools.EXPIRED_TAG] = True
49-
for task_node in homework_node[Tags.TASKS_TAG]:
50-
task = Task.from_yaml_node(
51-
task_node=task_node,
52-
student_hw_folder=current_folder,
53-
job_file=self._job_file_path,
54-
)
55-
if not task:
56-
continue
57-
results[hw_name][task.name] = task.check_all_tests()
52+
results[task.name] = task.check_all_tests()
53+
return results
5854

55+
def check_all_homeworks(self: "Checker") -> Dict[str, HomeworkResultDict]:
56+
"""Run over all Tasks in all homeworks."""
57+
results: Dict[str, HomeworkResultDict] = {}
58+
for homework_node in self._base_node[Tags.HOMEWORKS_TAG]:
59+
hw_name = homework_node[Tags.NAME_TAG]
60+
current_homework_results = self.check_homework(homework_node)
61+
if current_homework_results:
62+
results[hw_name] = current_homework_results
5963
return results

homework_checker/md_writer.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""Write test results into a markdown file."""
2+
from typing import Dict
3+
from pathlib import Path
24

35
from .tools import EXPIRED_TAG
6+
from .tools import CmdResult
7+
from .checker import HomeworkResultDict
48

59
TABLE_TEMPLATE = "| {hw_name} | {task_name} | {test_name} | {result_sign} |\n"
610
TABLE_SEPARATOR = "|---|---|---|:---:|\n"
@@ -34,7 +38,7 @@
3438
class MdWriter:
3539
"""Write given tests results into a markdown file."""
3640

37-
def __init__(self):
41+
def __init__(self: "MdWriter"):
3842
"""Initialize the writer."""
3943
self._md_table = TABLE_TEMPLATE.format(
4044
hw_name="Homework Name",
@@ -45,7 +49,7 @@ def __init__(self):
4549
self._md_table += TABLE_SEPARATOR
4650
self._errors = "" # Markdown part with errors.
4751

48-
def update(self, hw_results):
52+
def update(self: "MdWriter", hw_results: Dict[str, HomeworkResultDict]):
4953
"""Update the table of completion."""
5054
for hw_name, hw_dict in sorted(hw_results.items()):
5155
need_hw_name = True
@@ -74,7 +78,7 @@ def update(self, hw_results):
7478
need_hw_name = False # We only print homework name once.
7579
need_task_name = False # We only print Task name once.
7680

77-
def write_md_file(self, md_file_path):
81+
def write_md_file(self: "MdWriter", md_file_path: Path):
7882
"""Write all the added content to the md file."""
7983
md_file_content = "# Test results\n"
8084
md_file_content += self._md_table
@@ -86,7 +90,14 @@ def write_md_file(self, md_file_path):
8690
with open(md_file_path, "w") as md_file:
8791
md_file.write(md_file_content)
8892

89-
def _add_error(self, hw_name, task_name, test_name, test_result, expired):
93+
def _add_error(
94+
self: "MdWriter",
95+
hw_name: str,
96+
task_name: str,
97+
test_name: str,
98+
test_result: CmdResult,
99+
expired: bool,
100+
):
90101
"""Add a section of errors to the md file."""
91102
if test_result.succeeded():
92103
return

homework_checker/schema_manager.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
"""Manage creation of schema."""
2+
from __future__ import annotations
23
import sys
34
import logging
45
import operator
5-
from os import path
6-
from schema import Schema, SchemaError, Or, Optional
7-
from ruamel.yaml import YAML
8-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
6+
from pathlib import Path
7+
from typing import Union, List, Dict, Any
8+
from ruamel.yaml import YAML # type: ignore[import]
9+
from ruamel.yaml.comments import CommentedMap, CommentedSeq # type: ignore[import]
10+
from schema import Schema, SchemaError, Or, Optional # type: ignore[import]
911

1012
from .tools import MAX_DATE_STR
1113
from .schema_tags import Tags, OutputTags, BuildTags, LangTags
1214

1315
log = logging.getLogger("GHC")
1416

15-
SCHEMA_FILE = path.join(path.dirname(path.dirname(__file__)), "schema", "schema.yml")
17+
SCHEMA_FILE = Path(__file__).parent.parent / "schema" / "schema.yml"
1618

1719

1820
class SchemaManager:
1921
"""Manage schema creation."""
2022

21-
def __init__(self, file_name):
23+
GenericListType = List[Union[Any, Dict[str, Any], List[Any]]]
24+
GenericDictType = Dict[str, Union[Any, Dict[str, Any], List[Any]]]
25+
26+
def __init__(self: SchemaManager, file_name: Path):
2227
"""Create a schema for my tests."""
28+
# TODO(igor): simplify by moving parts out of here into separate variables.
2329
self.__schema = Schema(
2430
{
2531
Tags.FOLDER_TAG: str,
@@ -63,8 +69,9 @@ def __init__(self, file_name):
6369
}
6470
)
6571
yaml = YAML()
66-
yaml.width = 4096 # big enough value to prevent wrapping
67-
yaml.explicit_start = True
72+
# big enough value to prevent wrapping
73+
yaml.width = 4096 # type: ignore[assignment]
74+
yaml.explicit_start = True # type: ignore[assignment]
6875
yaml.indent(mapping=2, sequence=4, offset=2)
6976
with open(file_name, "r") as stream:
7077
contents = SchemaManager.__to_simple_dict(yaml.load(stream))
@@ -83,8 +90,11 @@ def __init__(self, file_name):
8390
except OSError:
8491
log.debug("Cannot write schema file. We only use this while developing.")
8592

86-
def __to_simple_list(commented_seq):
87-
simple_list = []
93+
@staticmethod
94+
def __to_simple_list(
95+
commented_seq: List[Union[Any, CommentedSeq, CommentedMap]]
96+
) -> SchemaManager.GenericListType:
97+
simple_list: SchemaManager.GenericListType = []
8898
for value in commented_seq:
8999
if isinstance(value, CommentedSeq):
90100
simple_list.append(SchemaManager.__to_simple_list(value))
@@ -94,8 +104,11 @@ def __to_simple_list(commented_seq):
94104
simple_list.append(value)
95105
return simple_list
96106

97-
def __to_simple_dict(commented_map):
98-
simple_dict = {}
107+
@staticmethod
108+
def __to_simple_dict(
109+
commented_map: Dict[str, Union[Any, CommentedSeq, CommentedMap]],
110+
) -> SchemaManager.GenericDictType:
111+
simple_dict: SchemaManager.GenericDictType = {}
99112
for key, value in commented_map.items():
100113
if isinstance(value, CommentedMap):
101114
simple_dict[key] = SchemaManager.__to_simple_dict(value)
@@ -106,7 +119,7 @@ def __to_simple_dict(commented_map):
106119
return simple_dict
107120

108121
@property
109-
def validated_yaml(self):
122+
def validated_yaml(self) -> dict:
110123
"""Return validated yaml."""
111124
return self.__validated_yaml
112125

@@ -115,9 +128,13 @@ def schema(self):
115128
"""Return schema."""
116129
return self.__schema
117130

131+
# pylint: disable=R0911
132+
# This method needs this many returns.
118133
@staticmethod
119-
def __sanitize_value(input_var):
134+
def __sanitize_value(input_var: Union[dict, Optional, Or, Any]):
120135
"""Use the schema and create an example file from it."""
136+
# pylint: disable=W0212
137+
# This seems to be the only way to get to schema value.
121138
if isinstance(input_var, dict):
122139
new_dict = {}
123140
for key, val in input_var.items():

homework_checker/schema_tags.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Collection of schema related tags."""
2+
from typing import Any
23

34

45
class Tags:
@@ -49,14 +50,14 @@ class LangTags:
4950
class OneOf:
5051
"""Check that an item is one of the list."""
5152

52-
def __init__(self, some_list):
53+
def __init__(self: "OneOf", some_list: list):
5354
"""Set the list to choose from."""
5455
self.__items = some_list
5556

56-
def __call__(self, item):
57+
def __call__(self: "OneOf", item: Any):
5758
"""Check that the list contains what is needed."""
5859
return item in self.__items
5960

60-
def __str__(self):
61+
def __str__(self: "OneOf"):
6162
"""Override str for this class."""
6263
return "Possible values: {}".format(self.__items)

0 commit comments

Comments
 (0)