0
# Utilities
1
2
Essential utility functions and classes that provide core infrastructure for webassets, including path operations, context managers, object resolution, registry management, and security features.
3
4
## Capabilities
5
6
### Hash Functions
7
8
```python { .api }
9
def hash_func(data):
10
"""
11
Create hash from arbitrary data using webassets cache system.
12
13
Args:
14
data: Data to hash (can be complex objects)
15
16
Returns:
17
str: MD5 hash string
18
"""
19
20
md5_constructor = hashlib.md5
21
```
22
23
### Path Operations
24
25
```python { .api }
26
def common_path_prefix(paths, sep=os.path.sep):
27
"""
28
Find common directory path prefix from multiple paths.
29
30
Improved version of os.path.commonpath() that handles both
31
forward and backward slashes correctly.
32
33
Args:
34
paths: List of file paths
35
sep: Path separator to use in result (default: os.path.sep)
36
37
Returns:
38
str: Common directory prefix
39
"""
40
41
def is_url(s):
42
"""
43
Check if string is a valid URL.
44
45
Args:
46
s: String to check
47
48
Returns:
49
bool: True if string has valid URL scheme and netloc
50
"""
51
```
52
53
### Context Managers
54
55
```python { .api }
56
@contextlib.contextmanager
57
def working_directory(directory=None, filename=None):
58
"""
59
Context manager to temporarily change working directory.
60
61
Useful for filters that need to run in specific directories.
62
Restores original directory on exit.
63
64
Args:
65
directory: Directory to change to
66
filename: File path (directory will be extracted)
67
68
Note: Exactly one of directory or filename must be provided.
69
70
Usage:
71
with working_directory('/path/to/dir'):
72
# commands run in /path/to/dir
73
# back to original directory
74
"""
75
```
76
77
### Object Resolution System
78
79
```python { .api }
80
def make_option_resolver(clazz=None, attribute=None, classes=None,
81
allow_none=True, desc=None):
82
"""
83
Create a function that resolves options to objects.
84
85
The resolver can handle:
86
- Object instances (returned as-is)
87
- Class objects (instantiated)
88
- String identifiers (resolved from registry)
89
- Arguments in format "key:argument"
90
91
Args:
92
clazz: Base class for type checking
93
attribute: Duck-typing attribute to check for
94
classes: Registry dictionary for string resolution
95
allow_none: Whether None values are allowed
96
desc: Description for error messages
97
98
Returns:
99
function: Resolver function that takes (option, env=None)
100
"""
101
```
102
103
### Registry Metaclass
104
105
```python { .api }
106
def RegistryMetaclass(clazz=None, attribute=None, allow_none=True, desc=None):
107
"""
108
Create a metaclass that maintains a registry of subclasses.
109
110
Classes using this metaclass are automatically registered by their
111
'id' attribute and can be resolved from strings.
112
113
Args:
114
clazz: Base class for type checking
115
attribute: Duck-typing attribute
116
allow_none: Whether None resolution is allowed
117
desc: Description for error messages
118
119
Returns:
120
type: Metaclass with REGISTRY dict and resolve() method
121
122
Features:
123
- Automatic subclass registration by 'id' attribute
124
- String-to-class resolution via resolve() method
125
- Automatic __eq__, __str__, __unicode__ methods
126
- Integration with make_option_resolver
127
"""
128
```
129
130
### Debug Level Management
131
132
```python { .api }
133
def cmp_debug_levels(level1, level2):
134
"""
135
Compare debug levels for filter execution.
136
137
Debug levels: False < 'merge' < True
138
139
Args:
140
level1: First debug level
141
level2: Second debug level
142
143
Returns:
144
int: -1 if level1 < level2, 0 if equal, 1 if level1 > level2
145
146
Raises:
147
BundleError: If invalid debug level provided
148
"""
149
```
150
151
### Security Functions
152
153
```python { .api }
154
def calculate_sri(data):
155
"""
156
Calculate Subresource Integrity (SRI) hash for data.
157
158
Args:
159
data: Bytes data to hash
160
161
Returns:
162
str: SRI string in format 'sha384-<base64hash>'
163
"""
164
165
def calculate_sri_on_file(file_name):
166
"""
167
Calculate SRI hash for file contents.
168
169
Args:
170
file_name: Path to file
171
172
Returns:
173
str or None: SRI string, or None if file not found
174
"""
175
```
176
177
### Module Exports
178
179
```python { .api }
180
# Compatibility imports for different Python versions
181
md5_constructor = hashlib.md5
182
set = set # Built-in set for older Python versions
183
StringIO = io.StringIO # String buffer for text operations
184
pickle = pickle # Serialization module
185
```
186
187
## Key Features
188
189
### Flexible Object Resolution
190
191
The option resolver system enables flexible configuration:
192
193
```python
194
# String resolution with arguments
195
resolver = make_option_resolver(classes={'file': FileCache, 'mem': MemoryCache})
196
cache = resolver('file:/tmp/cache', env) # Creates FileCache('/tmp/cache')
197
198
# Class resolution with factory method
199
cache = resolver(FileCache, env) # Uses FileCache.make(env) if available
200
201
# Instance resolution
202
cache = resolver(existing_cache) # Returns existing_cache as-is
203
```
204
205
### Registry Pattern
206
207
The metaclass provides automatic registration:
208
209
```python
210
class BaseUpdater(metaclass=RegistryMetaclass(desc='updater')):
211
pass
212
213
class TimestampUpdater(BaseUpdater):
214
id = 'timestamp' # Automatically registered
215
216
# String resolution
217
updater = BaseUpdater.resolve('timestamp') # Returns TimestampUpdater instance
218
```
219
220
### Cross-Platform Path Handling
221
222
Path utilities work consistently across operating systems:
223
224
```python
225
# Handles mixed separators correctly
226
paths = ['/home/user/project\\assets\\css', '/home/user/project/assets/js']
227
common = common_path_prefix(paths) # '/home/user/project/assets'
228
```
229
230
### Safe Working Directory Changes
231
232
Context manager ensures directory restoration even on exceptions:
233
234
```python
235
original_dir = os.getcwd()
236
with working_directory('/tmp/build'):
237
# Process files in /tmp/build
238
run_build_command()
239
# Exception here still restores directory
240
# Back in original_dir
241
```
242
243
### Security Integration
244
245
SRI support for Content Security Policy compliance:
246
247
```python
248
# Generate SRI for inline content
249
content = "console.log('hello');"
250
sri = calculate_sri(content.encode('utf-8'))
251
# Use in HTML: <script integrity="sha384-...">
252
253
# Generate SRI for files
254
sri = calculate_sri_on_file('app.js')
255
if sri:
256
print(f'<script src="app.js" integrity="{sri}"></script>')
257
```
258
259
## Usage Examples
260
261
### Factory Pattern with Environment
262
263
```python
264
class CustomCache:
265
@classmethod
266
def make(cls, env, path):
267
# Initialize with environment-specific settings
268
return cls(path, debug=env.debug)
269
270
resolver = make_option_resolver(classes={'custom': CustomCache})
271
cache = resolver('custom:/tmp/cache', env) # Uses make() method
272
```
273
274
### Registry-Based Plugin System
275
276
```python
277
class FilterBase(metaclass=RegistryMetaclass(desc='filter')):
278
def apply(self, content):
279
raise NotImplementedError
280
281
class JSMinFilter(FilterBase):
282
id = 'jsmin'
283
def apply(self, content):
284
return minify_js(content)
285
286
# Automatic resolution
287
filter_obj = FilterBase.resolve('jsmin') # Gets JSMinFilter instance
288
```
289
290
### Debug Level Filtering
291
292
```python
293
# Filter selection based on debug level
294
available_filters = [dev_filter, prod_filter, debug_filter]
295
current_level = 'merge'
296
297
active_filters = [f for f in available_filters
298
if cmp_debug_levels(current_level, f.max_debug_level) <= 0]
299
```