0
# File and Path Utilities
1
2
Helper functions for working with files, directories, and path filtering. These utilities support project discovery, configuration file location, and file filtering based on include/exclude patterns.
3
4
## Capabilities
5
6
### Project Configuration Discovery
7
8
Functions for finding project configuration files and roots.
9
10
```python { .api }
11
def find_pyproject_toml(path_search_start: tuple[str, ...]) -> str | None:
12
"""
13
Find the absolute filepath to a pyproject.toml if it exists.
14
15
Searches upward from the given starting paths to locate the project
16
root and check for pyproject.toml configuration.
17
18
Parameters:
19
- path_search_start: Tuple of starting paths to search from
20
21
Returns:
22
Absolute path to pyproject.toml file, or None if not found
23
"""
24
```
25
26
### File Filtering Constants
27
28
Pre-compiled regular expressions for filtering Python files and excluding common directories.
29
30
```python { .api }
31
DEFAULT_EXCLUDE_RE: Pattern[str]
32
"""
33
Regular expression for default exclusion patterns.
34
35
Excludes common directories like:
36
- .git, .hg, .svn (version control)
37
- .mypy_cache, .pytest_cache, .ruff_cache (tool caches)
38
- .tox, .nox (testing environments)
39
- __pycache__, .eggs (Python artifacts)
40
- build, dist, _build (build outputs)
41
- .venv, venv (virtual environments)
42
- .vscode, .direnv (editor/tool directories)
43
"""
44
45
DEFAULT_INCLUDE_RE: Pattern[str]
46
"""
47
Regular expression for default inclusion patterns.
48
49
Includes files with extensions:
50
- .py (Python files)
51
- .pyi (Python stub files)
52
- .ipynb (Jupyter notebooks)
53
"""
54
```
55
56
### Path Processing Functions
57
58
Functions for processing and filtering file paths.
59
60
```python { .api }
61
def filter_python_files_directory(
62
paths: Collection[Path],
63
root: Path,
64
include: Pattern[str],
65
exclude: Pattern[str],
66
extend_exclude: Pattern[str] | None,
67
force_exclude: Pattern[str] | None,
68
formatter: BaseFormatter,
69
) -> Iterator[Path]:
70
"""
71
Filter Python files in directories based on include/exclude patterns.
72
73
Parameters:
74
- paths: Collection of file/directory paths to process
75
- root: Root directory for relative path calculations
76
- include: Pattern for files to include
77
- exclude: Pattern for files/directories to exclude
78
- extend_exclude: Additional exclusion pattern
79
- force_exclude: Pattern that overrides all inclusion rules
80
- formatter: Formatter instance for additional filtering
81
82
Yields:
83
Path objects for Python files that match the filtering criteria
84
"""
85
86
def gen_python_files(
87
paths: Collection[Path],
88
root: Path,
89
include: Pattern[str],
90
exclude: Pattern[str],
91
extend_exclude: Pattern[str] | None = None,
92
force_exclude: Pattern[str] | None = None,
93
) -> Iterator[Path]:
94
"""
95
Generate Python file paths from given directories and files.
96
97
Parameters:
98
- paths: Paths to files/directories to process
99
- root: Project root directory
100
- include: Pattern for files to include
101
- exclude: Pattern for files/directories to exclude
102
- extend_exclude: Additional exclusion pattern
103
- force_exclude: Pattern that overrides inclusion rules
104
105
Yields:
106
Path objects for Python files matching the criteria
107
"""
108
```
109
110
## Usage Examples
111
112
### Finding Project Configuration
113
114
```python
115
from darker.files import find_pyproject_toml
116
117
# Find pyproject.toml starting from current directory
118
config_path = find_pyproject_toml((".",))
119
120
if config_path:
121
print(f"Found project config: {config_path}")
122
123
# Read configuration
124
import tomllib
125
with open(config_path, 'rb') as f:
126
config = tomllib.load(f)
127
128
darker_config = config.get('tool', {}).get('darker', {})
129
print(f"Darker config: {darker_config}")
130
else:
131
print("No pyproject.toml found")
132
```
133
134
### Using Default Filter Patterns
135
136
```python
137
from darker.files import DEFAULT_INCLUDE_RE, DEFAULT_EXCLUDE_RE
138
from pathlib import Path
139
140
# Test if files match default patterns
141
test_files = [
142
"src/module.py",
143
"tests/test_module.py",
144
"setup.py",
145
"README.md",
146
".git/config",
147
"__pycache__/module.pyc",
148
"dist/package.whl"
149
]
150
151
for file_path in test_files:
152
path = Path(file_path)
153
154
# Check inclusion
155
includes = DEFAULT_INCLUDE_RE.search(str(path))
156
157
# Check exclusion
158
excludes = DEFAULT_EXCLUDE_RE.search(str(path))
159
160
if includes and not excludes:
161
print(f"✓ Would process: {file_path}")
162
else:
163
reason = "excluded" if excludes else "not Python file"
164
print(f"✗ Would skip: {file_path} ({reason})")
165
```
166
167
### Custom File Filtering
168
169
```python
170
import re
171
from darker.files import gen_python_files
172
from pathlib import Path
173
174
# Create custom patterns
175
custom_include = re.compile(r'(\.py|\.pyx)$') # Include Cython files
176
custom_exclude = re.compile(r'/(build|dist|\.tox)/')
177
custom_extend_exclude = re.compile(r'/legacy/') # Skip legacy code
178
179
# Find Python files with custom filters
180
root = Path(".")
181
paths = [Path("src"), Path("tests")]
182
183
python_files = list(gen_python_files(
184
paths=paths,
185
root=root,
186
include=custom_include,
187
exclude=custom_exclude,
188
extend_exclude=custom_extend_exclude
189
))
190
191
print(f"Found {len(python_files)} Python files:")
192
for file_path in python_files:
193
print(f" {file_path}")
194
```
195
196
### Integration with Formatter Filtering
197
198
```python
199
from darker.files import filter_python_files_directory
200
from darker.formatters import create_formatter
201
from pathlib import Path
202
import re
203
204
# Set up formatter and patterns
205
formatter = create_formatter("black")
206
root = Path(".")
207
paths = [Path("src"), Path("tests")]
208
209
include_pattern = re.compile(r'\.pyi?$')
210
exclude_pattern = re.compile(r'/(\.git|__pycache__|\.pytest_cache)/')
211
212
# Filter files that the formatter can process
213
filtered_files = list(filter_python_files_directory(
214
paths=paths,
215
root=root,
216
include=include_pattern,
217
exclude=exclude_pattern,
218
extend_exclude=None,
219
force_exclude=None,
220
formatter=formatter
221
))
222
223
print(f"Files to format with {formatter.name}:")
224
for file_path in filtered_files:
225
print(f" {file_path}")
226
```
227
228
### Recursive Directory Processing
229
230
```python
231
from darker.files import gen_python_files, DEFAULT_INCLUDE_RE, DEFAULT_EXCLUDE_RE
232
from pathlib import Path
233
234
def find_all_python_files(start_path: Path) -> list[Path]:
235
"""Find all Python files recursively from a starting path."""
236
237
if start_path.is_file():
238
# Single file
239
if DEFAULT_INCLUDE_RE.search(str(start_path)):
240
return [start_path]
241
else:
242
return []
243
244
# Directory - search recursively
245
return list(gen_python_files(
246
paths=[start_path],
247
root=start_path,
248
include=DEFAULT_INCLUDE_RE,
249
exclude=DEFAULT_EXCLUDE_RE
250
))
251
252
# Usage examples
253
project_files = find_all_python_files(Path("src"))
254
test_files = find_all_python_files(Path("tests"))
255
single_file = find_all_python_files(Path("setup.py"))
256
257
print(f"Project files: {len(project_files)}")
258
print(f"Test files: {len(test_files)}")
259
print(f"Single file: {len(single_file)}")
260
```