0
# Utilities
1
2
Additional utility functions for archive handling, file operations, and other common setuptools operations. These utilities provide helpful functionality for package building, file management, and specialized string handling.
3
4
## Capabilities
5
6
### Archive Utilities
7
8
Functions for extracting and handling various archive formats commonly used in Python packaging.
9
10
```python { .api }
11
def unpack_archive(filename, extract_dir='.', drivers=None):
12
"""
13
Extract various archive formats to a directory.
14
15
Automatically detects archive format and extracts using the
16
appropriate method. Supports zip, tar, tar.gz, tar.bz2, and other formats.
17
18
Parameters:
19
- filename (str): Path to archive file to extract
20
- extract_dir (str): Directory to extract files to (default: current directory)
21
- drivers (list, optional): List of extraction drivers to try
22
23
Returns:
24
None
25
26
Raises:
27
UnpackError: If extraction fails
28
"""
29
30
def unpack_zipfile(filename, extract_dir='.'):
31
"""
32
Extract ZIP archive to a directory.
33
34
Specialized function for extracting ZIP files with proper
35
handling of file permissions and directory structure.
36
37
Parameters:
38
- filename (str): Path to ZIP file
39
- extract_dir (str): Directory to extract to (default: current directory)
40
41
Returns:
42
None
43
44
Raises:
45
UnpackError: If ZIP extraction fails
46
"""
47
48
def unpack_tarfile(filename, extract_dir='.'):
49
"""
50
Extract TAR archive (including compressed variants) to a directory.
51
52
Handles tar, tar.gz, tar.bz2, and tar.xz files with proper
53
permission and timestamp preservation.
54
55
Parameters:
56
- filename (str): Path to TAR file
57
- extract_dir (str): Directory to extract to (default: current directory)
58
59
Returns:
60
None
61
62
Raises:
63
UnpackError: If TAR extraction fails
64
"""
65
66
def default_filter(src, dst):
67
"""
68
Default filter function for archive extraction.
69
70
Provides security filtering for archive extraction to prevent
71
directory traversal attacks and other security issues.
72
73
Parameters:
74
- src (str): Source path within archive
75
- dst (str): Destination path for extraction
76
77
Returns:
78
str: Filtered destination path, or None to skip file
79
80
This function is used internally by the unpack functions to
81
ensure safe extraction by filtering out dangerous paths.
82
"""
83
84
def unpack_directory(filename, extract_dir='.'):
85
"""
86
Process a directory as if it were an archive.
87
88
Simply copies directory contents, used internally by unpack_archive
89
when the "archive" is actually a directory.
90
91
Parameters:
92
- filename (str): Path to directory to process
93
- extract_dir (str): Directory to copy contents to
94
95
Returns:
96
None
97
"""
98
```
99
100
### File Utilities
101
102
Functions for file system operations and comparisons.
103
104
```python { .api }
105
def newer(source, target):
106
"""
107
Check if source file is newer than target file.
108
109
Parameters:
110
- source (str): Path to source file
111
- target (str): Path to target file
112
113
Returns:
114
bool: True if source is newer than target or target doesn't exist
115
"""
116
117
def newer_pairwise(sources_and_targets):
118
"""
119
Check if each source is newer than its corresponding target.
120
121
Parameters:
122
- sources_and_targets (list): List of (source, target) tuples
123
124
Returns:
125
list[bool]: List of boolean results for each pair
126
"""
127
128
def newer_group(sources, target, missing='error'):
129
"""
130
Check if any source file is newer than target.
131
132
Parameters:
133
- sources (list): List of source file paths
134
- target (str): Path to target file
135
- missing (str): How to handle missing source files ('error', 'ignore', 'newer')
136
137
Returns:
138
bool: True if any source is newer than target
139
"""
140
141
def newer_pairwise_group(sources_and_targets):
142
"""
143
Check if any source is newer than any target in groups.
144
145
Parameters:
146
- sources_and_targets (list): List of (sources_list, targets_list) tuples
147
148
Returns:
149
list[bool]: List of boolean results for each group
150
"""
151
```
152
153
### Globbing Functions
154
155
Pattern matching functions for file discovery.
156
157
```python { .api }
158
def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False):
159
"""
160
Find pathnames matching a shell-style pattern.
161
162
Enhanced version of standard glob.glob with setuptools-specific behavior.
163
164
Parameters:
165
- pathname (str): Pattern to match (supports *, ?, [seq], **/)
166
- root_dir (str, optional): Root directory for relative patterns
167
- dir_fd (int, optional): File descriptor for directory
168
- recursive (bool): Whether ** matches directories recursively
169
170
Returns:
171
list[str]: List of matching paths
172
"""
173
174
def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False):
175
"""
176
Iterator version of glob().
177
178
Parameters: Same as glob()
179
180
Returns:
181
Iterator[str]: Iterator over matching paths
182
"""
183
184
def escape(pathname):
185
"""
186
Escape special characters in pathname for literal matching.
187
188
Parameters:
189
- pathname (str): Path that may contain special glob characters
190
191
Returns:
192
str: Escaped pathname for literal matching
193
"""
194
```
195
196
### Configuration Functions
197
198
Functions for parsing and handling setuptools configuration files.
199
200
```python { .api }
201
def parse_configuration(filepath):
202
"""
203
Parse setup.cfg configuration file.
204
205
DEPRECATED: Use pyproject.toml instead of setup.cfg for new projects.
206
207
Parameters:
208
- filepath (str): Path to setup.cfg file
209
210
Returns:
211
dict: Parsed configuration data
212
213
Raises:
214
DistutilsFileError: If configuration file cannot be read
215
"""
216
217
def read_configuration(filepath, find_others=False, ignore_option_errors=False):
218
"""
219
Read and parse setup.cfg and other configuration files.
220
221
DEPRECATED: Use pyproject.toml instead of setup.cfg for new projects.
222
223
Parameters:
224
- filepath (str): Path to main configuration file
225
- find_others (bool): Whether to find and parse related config files
226
- ignore_option_errors (bool): Whether to ignore invalid options
227
228
Returns:
229
dict: Combined configuration from all sources
230
"""
231
```
232
233
### String Utilities
234
235
Specialized string classes for setuptools operations.
236
237
```python { .api }
238
class sic(str):
239
"""
240
String class for literal treatment.
241
242
A string subclass that signals to setuptools that the string
243
should be treated literally without any special processing or
244
interpretation. Used internally for certain setuptools operations
245
where exact string preservation is required.
246
247
Example:
248
literal_string = sic("some_literal_value")
249
"""
250
251
def __new__(cls, s=''):
252
"""
253
Create new sic string instance.
254
255
Parameters:
256
- s (str): String value to wrap
257
258
Returns:
259
sic: New sic string instance
260
"""
261
```
262
263
### Version Information
264
265
Access to setuptools version information.
266
267
```python { .api }
268
__version__: str
269
"""
270
Current setuptools version string.
271
272
Example: "68.2.2"
273
This can be used to check setuptools version at runtime
274
and implement version-specific behavior.
275
"""
276
```
277
278
### Package Discovery Classes
279
280
Advanced package discovery classes that power the find_packages functions.
281
282
```python { .api }
283
class PackageFinder:
284
"""
285
Standard package discovery class.
286
287
Provides the underlying implementation for find_packages()
288
with methods for discovering regular Python packages.
289
"""
290
291
@staticmethod
292
def find(where='.', exclude=(), include=('*',)):
293
"""
294
Find Python packages in directory tree.
295
296
Parameters:
297
- where (str): Root directory to search
298
- exclude (tuple): Patterns to exclude
299
- include (tuple): Patterns to include
300
301
Returns:
302
list: List of package names
303
"""
304
305
class PEP420PackageFinder:
306
"""
307
PEP 420 namespace package discovery class.
308
309
Provides the underlying implementation for find_namespace_packages()
310
with methods for discovering implicit namespace packages.
311
"""
312
313
@staticmethod
314
def find(where='.', exclude=(), include=('*',)):
315
"""
316
Find PEP 420 namespace packages in directory tree.
317
318
Parameters:
319
- where (str): Root directory to search
320
- exclude (tuple): Patterns to exclude
321
- include (tuple): Patterns to include
322
323
Returns:
324
list: List of namespace package names
325
"""
326
```
327
328
### Requirement Management
329
330
Classes for handling package requirements and dependencies.
331
332
```python { .api }
333
class Require:
334
"""
335
Represents a prerequisite for building or installing a distribution.
336
337
This class encapsulates information about required packages or
338
modules and provides methods to check their availability and versions.
339
340
Attributes:
341
- name (str): Requirement name
342
- requested_version (str): Requested version specification
343
- module_name (str): Python module name to check
344
- homepage (str): Homepage URL for the requirement
345
- attribute (str): Module attribute to check for version
346
- format (str): Version format specification
347
"""
348
349
def __init__(self, name, requested_version, module_name, homepage='',
350
attribute=None, format=None):
351
"""
352
Initialize requirement specification.
353
354
Parameters:
355
- name (str): Human-readable name of requirement
356
- requested_version (str): Version specification (e.g., ">=1.0")
357
- module_name (str): Python module name to import and check
358
- homepage (str): URL for requirement homepage
359
- attribute (str): Module attribute containing version info
360
- format (str): Format string for version extraction
361
"""
362
363
def version_ok(self, version):
364
"""
365
Check if a version satisfies this requirement.
366
367
Parameters:
368
- version (str): Version string to check
369
370
Returns:
371
bool: True if version satisfies requirement
372
"""
373
374
def get_version(self, paths=None, default='unknown'):
375
"""
376
Get version of installed requirement.
377
378
Parameters:
379
- paths (list, optional): Paths to search for module
380
- default (str): Default version if not found
381
382
Returns:
383
str: Version string of installed requirement
384
"""
385
386
def is_present(self, paths=None):
387
"""
388
Check if requirement is present (importable).
389
390
Parameters:
391
- paths (list, optional): Paths to search for module
392
393
Returns:
394
bool: True if requirement is present
395
"""
396
397
def is_current(self, paths=None):
398
"""
399
Check if requirement is present and version is current.
400
401
Parameters:
402
- paths (list, optional): Paths to search for module
403
404
Returns:
405
bool: True if requirement is present and satisfies version
406
"""
407
```
408
409
## Usage Examples
410
411
### Archive Extraction
412
413
```python
414
from setuptools.archive_util import unpack_archive, unpack_zipfile, unpack_tarfile
415
416
# Extract any supported archive format
417
unpack_archive('package-1.0.tar.gz', 'extracted/')
418
419
# Extract specific formats
420
unpack_zipfile('package-1.0.zip', 'build/zip_contents/')
421
unpack_tarfile('package-1.0.tar.gz', 'build/tar_contents/')
422
423
# Extract to current directory
424
unpack_archive('dependency.tar.bz2')
425
```
426
427
### Custom Package Discovery
428
429
```python
430
from setuptools.discovery import PackageFinder, PEP420PackageFinder
431
432
# Use package finders directly
433
finder = PackageFinder()
434
packages = finder.find(where='src', exclude=['tests*'])
435
436
namespace_finder = PEP420PackageFinder()
437
ns_packages = namespace_finder.find(where='src', include=['company.*'])
438
439
print(f"Regular packages: {packages}")
440
print(f"Namespace packages: {ns_packages}")
441
```
442
443
### Requirement Checking
444
445
```python
446
from setuptools.depends import Require
447
448
# Define a requirement
449
numpy_req = Require(
450
name='NumPy',
451
requested_version='>=1.19.0',
452
module_name='numpy',
453
homepage='https://numpy.org',
454
attribute='__version__'
455
)
456
457
# Check if requirement is satisfied
458
if numpy_req.is_present():
459
version = numpy_req.get_version()
460
if numpy_req.version_ok(version):
461
print(f"NumPy {version} is available and satisfies requirement")
462
else:
463
print(f"NumPy {version} is available but doesn't satisfy requirement")
464
else:
465
print("NumPy is not available")
466
467
# Check current status
468
if numpy_req.is_current():
469
print("NumPy requirement is fully satisfied")
470
else:
471
print("NumPy requirement is not satisfied")
472
```
473
474
### Version Checking
475
476
```python
477
import setuptools
478
479
# Check setuptools version
480
print(f"Using setuptools version: {setuptools.__version__}")
481
482
# Version-specific behavior
483
from packaging import version
484
if version.parse(setuptools.__version__) >= version.parse("61.0"):
485
print("Using modern setuptools with pyproject.toml support")
486
# Use modern features
487
else:
488
print("Using legacy setuptools")
489
# Fall back to legacy behavior
490
```
491
492
### Literal String Handling
493
494
```python
495
from setuptools import sic
496
497
# Create literal strings for special setuptools processing
498
literal_value = sic("--global-option")
499
processed_value = "processed_value"
500
501
# The sic class signals that the string should not be modified
502
# by setuptools internal processing
503
options = [literal_value, processed_value]
504
```
505
506
### Custom Archive Handling
507
508
```python
509
from setuptools.archive_util import unpack_archive, default_filter
510
import os
511
512
def safe_extract_with_filtering(archive_path, extract_dir):
513
"""Safely extract archive with custom filtering."""
514
515
def custom_filter(src, dst):
516
"""Custom filter to prevent certain extractions."""
517
# Use default security filtering
518
filtered_dst = default_filter(src, dst)
519
if filtered_dst is None:
520
return None
521
522
# Additional custom filtering
523
if src.startswith('dangerous/'):
524
print(f"Skipping dangerous path: {src}")
525
return None
526
527
return filtered_dst
528
529
# Extract with custom filtering
530
os.makedirs(extract_dir, exist_ok=True)
531
unpack_archive(archive_path, extract_dir)
532
print(f"Safely extracted {archive_path} to {extract_dir}")
533
```
534
535
### Build-time Archive Processing
536
537
```python
538
from setuptools import setup, Command
539
from setuptools.archive_util import unpack_archive
540
import tempfile
541
import shutil
542
543
class ExtractDataCommand(Command):
544
"""Custom command to extract data archives during build."""
545
546
description = 'Extract data archives'
547
user_options = [
548
('data-archive=', 'd', 'Path to data archive'),
549
('extract-dir=', 'e', 'Directory to extract to'),
550
]
551
552
def initialize_options(self):
553
self.data_archive = None
554
self.extract_dir = 'data'
555
556
def finalize_options(self):
557
if self.data_archive is None:
558
raise ValueError("data-archive option is required")
559
560
def run(self):
561
"""Extract data archive during build."""
562
self.announce(f'Extracting {self.data_archive} to {self.extract_dir}')
563
564
# Create extraction directory
565
os.makedirs(self.extract_dir, exist_ok=True)
566
567
# Extract archive
568
unpack_archive(self.data_archive, self.extract_dir)
569
570
self.announce(f'Extraction completed')
571
572
setup(
573
name='my-package',
574
cmdclass={
575
'extract_data': ExtractDataCommand,
576
},
577
)
578
```
579
580
### Development Utilities
581
582
```python
583
from setuptools import setup, find_packages
584
from setuptools.discovery import PackageFinder
585
import os
586
587
def find_packages_with_debug():
588
"""Find packages with debugging information."""
589
finder = PackageFinder()
590
591
# Find packages with detailed logging
592
print("Searching for packages...")
593
packages = finder.find(where='src')
594
595
for package in packages:
596
package_path = os.path.join('src', package.replace('.', os.sep))
597
print(f"Found package: {package} at {package_path}")
598
599
return packages
600
601
# Use in setup
602
setup(
603
name='my-package',
604
packages=find_packages_with_debug(),
605
)
606
```