feat: check unnecessary fields (#12)
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				build / build (push) Successful in 3m8s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	build / build (push) Successful in 3m8s
				
			This commit is contained in:
		
							parent
							
								
									30a22dba49
								
							
						
					
					
						commit
						f556801884
					
				|  | @ -1,11 +1,15 @@ | ||||||
|  | import json | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Tuple, Type, cast | from typing import Any, Dict, Tuple, Type, cast | ||||||
| 
 | 
 | ||||||
| import inquirer | import inquirer | ||||||
| import tomli | import tomli | ||||||
| import yaml | import yaml | ||||||
|  | from pydantic import BaseModel | ||||||
| 
 | 
 | ||||||
| from joj3_config_generator.models import answer, joj1, repo, task | from joj3_config_generator.models import answer, joj1, repo, task | ||||||
|  | from joj3_config_generator.models.common import Memory, Time | ||||||
|  | from joj3_config_generator.utils.logger import logger | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def load_joj3_task_toml_answers() -> answer.Answers: | def load_joj3_task_toml_answers() -> answer.Answers: | ||||||
|  | @ -39,6 +43,115 @@ def load_joj1_yaml(yaml_path: Path) -> joj1.Config: | ||||||
| def load_joj3_toml( | def load_joj3_toml( | ||||||
|     root_path: Path, repo_toml_path: Path, task_toml_path: Path |     root_path: Path, repo_toml_path: Path, task_toml_path: Path | ||||||
| ) -> Tuple[repo.Config, task.Config]: | ) -> Tuple[repo.Config, task.Config]: | ||||||
|  |     def check_unnecessary_fields( | ||||||
|  |         pydantic_model_type: Type[BaseModel], | ||||||
|  |         input_dict: Dict[str, Any], | ||||||
|  |         file_path: Path, | ||||||
|  |         current_path: str = "", | ||||||
|  |     ) -> None: | ||||||
|  |         def format_value_for_toml_warning(value: Any) -> str: | ||||||
|  |             if isinstance(value, str): | ||||||
|  |                 escaped_value = value.replace("\\", "\\\\").replace('"', '\\"') | ||||||
|  |                 return f'"{escaped_value}"' | ||||||
|  |             elif isinstance(value, bool): | ||||||
|  |                 return str(value).lower() | ||||||
|  |             elif isinstance(value, (int, float)): | ||||||
|  |                 return str(value) | ||||||
|  |             elif isinstance(value, Path): | ||||||
|  |                 escaped_value = str(value).replace("\\", "\\\\").replace('"', '\\"') | ||||||
|  |                 return f'"{escaped_value}"' | ||||||
|  |             elif isinstance(value, list): | ||||||
|  |                 formatted_elements = [ | ||||||
|  |                     format_value_for_toml_warning(item) for item in value | ||||||
|  |                 ] | ||||||
|  |                 return f"[{', '.join(formatted_elements)}]" | ||||||
|  |             elif isinstance(value, dict): | ||||||
|  |                 return json.dumps(value, separators=(",", ":")) | ||||||
|  |             elif value is None: | ||||||
|  |                 return "None" | ||||||
|  |             else: | ||||||
|  |                 return repr(value) | ||||||
|  | 
 | ||||||
|  |         default_instance = pydantic_model_type.model_construct() | ||||||
|  |         for field_name, field_info in pydantic_model_type.model_fields.items(): | ||||||
|  |             should_warn = False | ||||||
|  |             full_field_path = ( | ||||||
|  |                 f"{current_path}.{field_name}" if current_path else field_name | ||||||
|  |             ) | ||||||
|  |             toml_field_name = field_name | ||||||
|  |             if field_info.alias in input_dict: | ||||||
|  |                 toml_field_name = field_info.alias | ||||||
|  |             if toml_field_name not in input_dict: | ||||||
|  |                 continue | ||||||
|  |             toml_value = input_dict[toml_field_name] | ||||||
|  |             default_value = getattr(default_instance, field_name) | ||||||
|  |             # Handle List[Pydantic.BaseModel] | ||||||
|  |             if ( | ||||||
|  |                 field_info.annotation is not None | ||||||
|  |                 and hasattr(field_info.annotation, "__origin__") | ||||||
|  |                 and field_info.annotation.__origin__ is list | ||||||
|  |                 and hasattr(field_info.annotation, "__args__") | ||||||
|  |                 and len(field_info.annotation.__args__) == 1 | ||||||
|  |                 and isinstance(field_info.annotation.__args__[0], type) | ||||||
|  |                 and issubclass(field_info.annotation.__args__[0], BaseModel) | ||||||
|  |             ): | ||||||
|  |                 nested_model_type = field_info.annotation.__args__[0] | ||||||
|  |                 # Ensure the TOML value is a list (as expected for this type) | ||||||
|  |                 if isinstance(toml_value, list): | ||||||
|  |                     for i, toml_item in enumerate(toml_value): | ||||||
|  |                         if isinstance(toml_item, dict): | ||||||
|  |                             check_unnecessary_fields( | ||||||
|  |                                 nested_model_type, | ||||||
|  |                                 toml_item, | ||||||
|  |                                 file_path, | ||||||
|  |                                 f"{full_field_path}[{i}]", | ||||||
|  |                             ) | ||||||
|  |                 continue | ||||||
|  |             # Handle directly nested Pydantic models (non-list) | ||||||
|  |             if isinstance(field_info.annotation, type) and issubclass( | ||||||
|  |                 field_info.annotation, BaseModel | ||||||
|  |             ): | ||||||
|  |                 if isinstance(toml_value, dict): | ||||||
|  |                     check_unnecessary_fields( | ||||||
|  |                         field_info.annotation, | ||||||
|  |                         toml_value, | ||||||
|  |                         file_path, | ||||||
|  |                         full_field_path, | ||||||
|  |                     ) | ||||||
|  |                 continue | ||||||
|  |             # Handle Path type | ||||||
|  |             elif ( | ||||||
|  |                 isinstance(toml_value, str) | ||||||
|  |                 and isinstance(default_value, Path) | ||||||
|  |                 and Path(toml_value) == default_value | ||||||
|  |             ): | ||||||
|  |                 should_warn = True | ||||||
|  |             # Handle Time type | ||||||
|  |             elif isinstance(default_value, Time) and Time(toml_value) == default_value: | ||||||
|  |                 should_warn = True | ||||||
|  |             # Handle Memory type | ||||||
|  |             elif ( | ||||||
|  |                 isinstance(default_value, Memory) | ||||||
|  |                 and Memory(toml_value) == default_value | ||||||
|  |             ): | ||||||
|  |                 should_warn = True | ||||||
|  |             # Handle non-model list types (e.g., List[str], List[int]) | ||||||
|  |             elif ( | ||||||
|  |                 isinstance(toml_value, list) | ||||||
|  |                 and isinstance(default_value, list) | ||||||
|  |                 and toml_value == default_value | ||||||
|  |             ): | ||||||
|  |                 should_warn = True | ||||||
|  |             # Handle other basic types (str, int, float, bool, dict) | ||||||
|  |             elif toml_value == default_value and toml_value != {}: | ||||||
|  |                 should_warn = True | ||||||
|  |             if should_warn: | ||||||
|  |                 logger.warning( | ||||||
|  |                     f"In file {file_path}, unnecessary field " | ||||||
|  |                     f"`{full_field_path} = {format_value_for_toml_warning(toml_value)}`" | ||||||
|  |                     " can be removed as it matches the default value" | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|     repo_obj = tomli.loads(repo_toml_path.read_text()) |     repo_obj = tomli.loads(repo_toml_path.read_text()) | ||||||
|     task_obj = tomli.loads(task_toml_path.read_text()) |     task_obj = tomli.loads(task_toml_path.read_text()) | ||||||
|     repo_conf = repo.Config(**repo_obj) |     repo_conf = repo.Config(**repo_obj) | ||||||
|  | @ -47,4 +160,6 @@ def load_joj3_toml( | ||||||
|     task_conf = task.Config(**task_obj) |     task_conf = task.Config(**task_obj) | ||||||
|     task_conf.root = root_path |     task_conf.root = root_path | ||||||
|     task_conf.path = task_toml_path.relative_to(root_path) |     task_conf.path = task_toml_path.relative_to(root_path) | ||||||
|  |     check_unnecessary_fields(repo.Config, repo_obj, repo_toml_path) | ||||||
|  |     check_unnecessary_fields(task.Config, task_obj, task_toml_path) | ||||||
|     return repo_conf, task_conf |     return repo_conf, task_conf | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								tests/convert/unnecessary/repo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/convert/unnecessary/repo.toml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | force_skip_health_check_on_test = true | ||||||
|  | force_skip_teapot_on_test = true | ||||||
							
								
								
									
										95
									
								
								tests/convert/unnecessary/task.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								tests/convert/unnecessary/task.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | { | ||||||
|  |     "name": "hw7 ex2", | ||||||
|  |     "logPath": "/home/tt/.cache/joj3/joj3.log", | ||||||
|  |     "expireUnixTimestamp": 0, | ||||||
|  |     "effectiveUnixTimestamp": 0, | ||||||
|  |     "actorCsvPath": "/home/tt/.config/joj/students.csv", | ||||||
|  |     "maxTotalScore": 100, | ||||||
|  |     "stage": { | ||||||
|  |         "sandboxExecServer": "172.17.0.1:5051", | ||||||
|  |         "sandboxToken": "", | ||||||
|  |         "outputPath": "/tmp/joj3_result.json", | ||||||
|  |         "stages": [ | ||||||
|  |             { | ||||||
|  |                 "name": "[cq] Filelength", | ||||||
|  |                 "group": "cq", | ||||||
|  |                 "executor": { | ||||||
|  |                     "name": "sandbox", | ||||||
|  |                     "with": { | ||||||
|  |                         "default": { | ||||||
|  |                             "args": [ | ||||||
|  |                                 "./tools/filelength", | ||||||
|  |                                 "400", | ||||||
|  |                                 "300", | ||||||
|  |                                 "*.cpp", | ||||||
|  |                                 "*.h" | ||||||
|  |                             ], | ||||||
|  |                             "env": [ | ||||||
|  |                                 "PATH=/usr/bin:/bin:/usr/local/bin" | ||||||
|  |                             ], | ||||||
|  |                             "stdin": { | ||||||
|  |                                 "content": "" | ||||||
|  |                             }, | ||||||
|  |                             "stdout": { | ||||||
|  |                                 "name": "stdout", | ||||||
|  |                                 "max": 33554432, | ||||||
|  |                                 "pipe": true | ||||||
|  |                             }, | ||||||
|  |                             "stderr": { | ||||||
|  |                                 "name": "stderr", | ||||||
|  |                                 "max": 33554432, | ||||||
|  |                                 "pipe": true | ||||||
|  |                             }, | ||||||
|  |                             "cpuLimit": 1000000000, | ||||||
|  |                             "clockLimit": 2000000000, | ||||||
|  |                             "memoryLimit": 268435456, | ||||||
|  |                             "stackLimit": 0, | ||||||
|  |                             "procLimit": 50, | ||||||
|  |                             "cpuRateLimit": 0, | ||||||
|  |                             "cpuSetLimit": "", | ||||||
|  |                             "copyIn": { | ||||||
|  |                                 "tools/filelength": { | ||||||
|  |                                     "src": "/home/tt/.config/joj/tools/filelength" | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             "copyInCached": {}, | ||||||
|  |                             "copyInDir": ".", | ||||||
|  |                             "copyOut": [ | ||||||
|  |                                 "stdout", | ||||||
|  |                                 "stderr" | ||||||
|  |                             ], | ||||||
|  |                             "copyOutCached": [], | ||||||
|  |                             "copyOutMax": 0, | ||||||
|  |                             "copyOutDir": "", | ||||||
|  |                             "tty": false, | ||||||
|  |                             "strictMemoryLimit": false, | ||||||
|  |                             "dataSegmentLimit": false, | ||||||
|  |                             "addressSpaceLimit": false | ||||||
|  |                         }, | ||||||
|  |                         "cases": [] | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "parsers": [ | ||||||
|  |                     { | ||||||
|  |                         "name": "result-detail", | ||||||
|  |                         "with": { | ||||||
|  |                             "score": 0, | ||||||
|  |                             "comment": "", | ||||||
|  |                             "showExxecutorStatus": true, | ||||||
|  |                             "showExitStatus": true, | ||||||
|  |                             "showError": false, | ||||||
|  |                             "showTime": true, | ||||||
|  |                             "showMemory": true, | ||||||
|  |                             "showRuntime": true, | ||||||
|  |                             "showFiles": [], | ||||||
|  |                             "filesInCodeBlock": true, | ||||||
|  |                             "maxFileLength": 2048 | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |             } | ||||||
|  |         ], | ||||||
|  |         "preStages": [], | ||||||
|  |         "postStages": [] | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								tests/convert/unnecessary/task.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/convert/unnecessary/task.toml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | # general task configuration | ||||||
|  | task.name = "hw7 ex2" # task name | ||||||
|  | 
 | ||||||
|  | [[stages]] | ||||||
|  | name = "[cq] Filelength" | ||||||
|  | command = "./tools/filelength 400 300 *.cpp *.h" | ||||||
|  | files.import = ["tools/filelength"] | ||||||
|  | 
 | ||||||
|  | parsers = ["result-detail"] | ||||||
|  | result-detail.cpu_time = true | ||||||
|  | result-detail.time = true | ||||||
|  | result-detail.mem = true | ||||||
|  | result-detail.stdout = false | ||||||
|  | result-detail.stderr = false | ||||||
|  | result-detail.exit_status = true | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user