Coverage for src/pylint_sort_functions/utils/file_patterns.py: 100%
48 statements
« prev ^ index » next coverage.py v7.10.1, created at 2025-08-12 16:06 +0200
« prev ^ index » next coverage.py v7.10.1, created at 2025-08-12 16:06 +0200
1"""File pattern matching utilities for Python project analysis.
3This module provides utilities for finding Python files, detecting test files,
4and matching file patterns using glob patterns.
5"""
7import fnmatch
8from pathlib import Path
9from typing import Any
12def find_python_files(root_path: Path) -> list[Path]: # pylint: disable=function-should-be-private
13 """Find all Python files in a project directory.
15 Recursively searches for files with .py extension while skipping common
16 directories that should not be analyzed (build artifacts, virtual environments,
17 caches, etc.).
19 TODO: Make skip_dirs list configurable for project-specific needs.
21 :param root_path: Root directory to search for Python files
22 :type root_path: Path
23 :returns: List of paths to Python files
24 :rtype: list[Path]
25 """
26 python_files = []
28 # Directories to skip
29 skip_dirs = {
30 "__pycache__",
31 ".git",
32 ".tox",
33 ".pytest_cache",
34 ".mypy_cache",
35 "venv",
36 ".venv",
37 "env",
38 ".env",
39 "build",
40 "dist",
41 "*.egg-info",
42 "node_modules",
43 }
45 for item in root_path.rglob("*.py"):
46 # Skip if any parent directory should be skipped
47 if any(skip_dir in item.parts for skip_dir in skip_dirs):
48 continue
50 python_files.append(item)
52 return python_files
55def is_unittest_file( # pylint: disable=function-should-be-private,too-many-return-statements,too-many-branches
56 module_name: str, privacy_config: dict[str, Any] | None = None
57) -> bool:
58 """Check if a module name indicates a unit test file.
60 Detects test files based on configurable patterns and built-in heuristics.
61 Can be configured to override built-in detection or add additional patterns.
63 Built-in detection patterns:
64 - Files in 'tests' or 'test' directories
65 - Files starting with 'test_'
66 - Files ending with '_test'
67 - conftest.py files (pytest configuration)
68 - Files containing 'test' in their path components
70 :param module_name: The module name to check (e.g., 'package.tests.test_utils')
71 :type module_name: str
72 :param privacy_config: Privacy configuration with exclusion patterns
73 :type privacy_config: dict[str, Any] | None
74 :returns: True if module appears to be a test file
75 :rtype: bool
76 """
77 if privacy_config is None:
78 privacy_config = {}
80 # Get configuration options
81 exclude_dirs = privacy_config.get("exclude_dirs", [])
82 exclude_patterns = privacy_config.get("exclude_patterns", [])
83 additional_test_patterns = privacy_config.get("additional_test_patterns", [])
84 override_test_detection = privacy_config.get("override_test_detection", False)
86 # Check directory exclusions first
87 lower_name = module_name.lower()
88 parts = lower_name.split(".")
90 # Check if file is in an excluded directory
91 for exclude_dir in exclude_dirs:
92 if exclude_dir.lower() in parts:
93 return True
95 # Check file pattern exclusions
96 for pattern in exclude_patterns:
97 if _matches_file_pattern(module_name, pattern):
98 return True
100 # Check additional test patterns
101 for pattern in additional_test_patterns:
102 if _matches_file_pattern(module_name, pattern):
103 return True
105 # If override is enabled, only use configured patterns
106 if override_test_detection:
107 return False
109 # Built-in test detection (original logic)
110 # Check if any directory in the path is a test directory
111 if "tests" in parts or "test" in parts:
112 return True
114 # Get the file name (last component)
115 if parts:
116 filename = parts[-1]
118 # Check for common test file patterns
119 if filename.startswith("test_"):
120 return True
121 if filename.endswith("_test"):
122 return True
123 if filename == "conftest": # pytest configuration file
124 return True
126 # Fallback: check if 'test' appears anywhere (catches edge cases)
127 # This is more permissive but ensures we do not miss test files
128 return "test" in lower_name
131def _matches_file_pattern(module_name: str, pattern: str) -> bool:
132 """Check if a module name matches a file pattern.
134 Supports glob patterns for matching file names and paths.
136 :param module_name: Module name to check (e.g., 'package.tests.test_utils')
137 :type module_name: str
138 :param pattern: Glob pattern to match against (e.g., 'test_*.py', '*_test.py')
139 :type pattern: str
140 :returns: True if module name matches pattern
141 :rtype: bool
142 """
143 # Convert module name to filename-like format for pattern matching
144 # e.g., "package.tests.test_utils" -> "package/tests/test_utils.py"
145 file_path = module_name.replace(".", "/") + ".py"
147 # Also check just the module name for direct matching
148 parts = module_name.split(".")
149 if parts:
150 filename = parts[-1] + ".py" # Add .py for pattern matching
152 # Check both full path and just filename
153 return fnmatch.fnmatch(file_path, pattern) or fnmatch.fnmatch(filename, pattern)
155 return fnmatch.fnmatch(file_path, pattern) # pragma: no cover