Coverage for src / codeaudit / filehelpfunctions.py: 49%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-09 09:33 +0200

1""" 

2License GPLv3 or higher. 

3 

4(C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/ 

5 

6This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 

7 

8This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

9 

10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 

11 

12Helper functions for files 

13""" 

14 

15import ast 

16import os 

17import sys 

18import warnings 

19from pathlib import Path 

20 

21 

22def read_in_source_file(file_path): 

23 # Ensure file_path is a Path object 

24 file_path = Path(file_path) 

25 

26 if file_path.is_dir(): 

27 print( 

28 "Error: The given path is a directory.\nUse 'codeaudit filescan' to security audit Python files in a directory or PyPI package.\nThe 'codeaudit modulescan' command works per file only, not on a directory.\nUse codeaudit -h for help" 

29 ) 

30 sys.exit(1) 

31 

32 if file_path.suffix.lower() != ".py": 

33 print("Error: The given file is not a Python (.py) file.") 

34 sys.exit(1) 

35 

36 try: 

37 with file_path.open("r", encoding="utf-8") as f: 

38 return f.read() 

39 except Exception as e: 

40 print(f"Failed to read file: {e}") 

41 sys.exit(1) 

42 

43 

44def collect_python_source_files(directory): 

45 """ 

46 Collects all Python source files (.py) from a directory, including subdirectories, 

47 while skipping directories that are hidden (start with '.') or underscore-prefixed, 

48 or are in a predefined exclusion list. 

49 

50 Args: 

51 directory (str): The path to the directory to search. 

52 

53 Returns: 

54 list: A list of absolute paths to valid Python source files. 

55 """ 

56 EXCLUDE_DIRS = {"docs", "docker", "dist", "tests"} 

57 python_files = [] 

58 

59 for root, dirs, files in os.walk(directory): 

60 # Filter out unwanted directories 

61 dirs[:] = [ 

62 d 

63 for d in dirs 

64 if not (d.startswith(".") or d.startswith("_") or d in EXCLUDE_DIRS) 

65 ] 

66 

67 for file in files: 

68 if file.endswith(".py") and not file.startswith("."): 

69 full_path = os.path.join(root, file) 

70 if os.path.isfile(full_path): 

71 python_files.append(os.path.abspath(full_path)) 

72 # check if the file can be parsed using the AST 

73 final_file_list = [] 

74 for python_file in python_files: 

75 if is_ast_parsable(python_file): 

76 final_file_list.append(python_file) 

77 else: 

78 print( 

79 f"Error: {python_file} will be skipped due to syntax error while parsing into AST." 

80 ) 

81 return final_file_list 

82 

83 

84def get_filename_from_path(file_path): 

85 """ 

86 Extracts the file name (including extension) from a full path string. 

87 

88 Args: 

89 file_path (str): The full path to the file. 

90 

91 Returns: 

92 str: The file name. 

93 """ 

94 # return os.path.basename(file_path) 

95 return Path(file_path).name 

96 

97 

98def is_ast_parsable(file_path): 

99 """ 

100 Checks whether a Python file can be parsed using the AST module. 

101 

102 Args: 

103 file_path (str): Path to the Python source file. 

104 

105 Returns: 

106 bool: True if the file can be parsed without syntax errors, False otherwise. 

107 """ 

108 try: 

109 with open(file_path, "r", encoding="utf-8") as f: 

110 source = f.read() 

111 

112 with warnings.catch_warnings(): 

113 warnings.simplefilter("ignore", category=SyntaxWarning) 

114 ast.parse(source, filename=file_path) 

115 return True 

116 except (SyntaxError, UnicodeDecodeError, ValueError) as e: 

117 return False 

118 

119 

120def has_python_files(input_path): 

121 """ 

122 Check whether a directory contains at least one Python file. 

123 

124 Args: 

125 input_path (str | Path): Path to a directory. 

126 

127 Returns: 

128 bool: True if the directory contains at least one Python file, False otherwise. 

129 """ 

130 file_path = Path(input_path) 

131 

132 if not file_path.is_dir(): 

133 return False 

134 

135 files_to_check = collect_python_source_files(file_path) 

136 return len(files_to_check) > 0