0
# Utility Functions
1
2
Helper functions for directory operations, relative URL generation, and file system utilities. These functions support the core freezing operations and provide additional functionality for working with static sites.
3
4
## Capabilities
5
6
### Directory Operations
7
8
Functions for traversing and processing directory structures.
9
10
```python { .api }
11
def walk_directory(root, ignore=()):
12
"""
13
Walk directory and yield slash-separated paths relative to root.
14
15
Used to implement URL generator for static files and file discovery.
16
17
Parameters:
18
- root: Directory path to walk (str or Path)
19
- ignore: List of fnmatch patterns to ignore
20
21
Yields:
22
str: Relative file paths with forward slashes
23
24
Ignore patterns:
25
- Patterns with slash are matched against full path
26
- Patterns without slash match individual path components
27
- Patterns ending with '/' match directories
28
"""
29
```
30
31
### URL Generation
32
33
Functions for generating relative URLs and handling URL transformations.
34
35
```python { .api }
36
def relative_url_for(endpoint, **values):
37
"""
38
Like Flask's url_for but returns relative URLs when possible.
39
40
Absolute URLs (with _external=True or different subdomain) are unchanged.
41
Relative URLs are converted based on current request context path.
42
URLs ending with '/' get 'index.html' appended for static compatibility.
43
44
Parameters:
45
- endpoint: Flask endpoint name
46
- **values: URL parameters (same as url_for)
47
48
Returns:
49
str: Relative URL or absolute URL if necessary
50
51
Note: Should only be used with Frozen-Flask, not in live Flask apps
52
"""
53
```
54
55
## Usage Examples
56
57
### Directory Walking
58
59
```python
60
from flask_frozen import walk_directory
61
from pathlib import Path
62
63
# Basic directory traversal
64
static_dir = Path('static')
65
for filepath in walk_directory(static_dir):
66
print(f"Found file: {filepath}")
67
# Output: css/style.css, js/app.js, images/logo.png, etc.
68
```
69
70
### Directory Walking with Ignore Patterns
71
72
```python
73
# Ignore specific files and directories
74
ignore_patterns = [
75
'*.pyc', # Ignore Python bytecode
76
'*.pyo', # Ignore optimized bytecode
77
'__pycache__/', # Ignore Python cache directories
78
'node_modules/', # Ignore Node.js dependencies
79
'*.scss', # Ignore SCSS source files
80
'src/*', # Ignore entire src directory
81
'.DS_Store', # Ignore macOS system files
82
]
83
84
for filepath in walk_directory('assets', ignore=ignore_patterns):
85
print(f"Will be included: {filepath}")
86
```
87
88
### Complex Ignore Patterns
89
90
```python
91
# Advanced ignore patterns
92
patterns = [
93
# Pattern matching examples:
94
'*.log', # Matches any .log file
95
'temp/', # Matches temp directory
96
'draft-*', # Matches files starting with 'draft-'
97
'*/cache/*', # Matches cache directories anywhere
98
'build/*.map', # Matches .map files in build directory
99
'.git*', # Matches .git, .gitignore, etc.
100
]
101
102
# Custom file filtering
103
def custom_file_filter(root_dir):
104
files = []
105
for filepath in walk_directory(root_dir, ignore=patterns):
106
# Additional custom filtering
107
if not filepath.startswith('_'): # Skip files starting with underscore
108
files.append(filepath)
109
return files
110
```
111
112
### Relative URL Generation
113
114
```python
115
from flask import Flask, request
116
from flask_frozen import relative_url_for
117
118
app = Flask(__name__)
119
120
@app.route('/blog/<slug>/')
121
def blog_post(slug):
122
return f'Blog post: {slug}'
123
124
# In a template or view context
125
with app.test_request_context('/blog/python-tips/'):
126
# From /blog/python-tips/ to /blog/flask-guide/
127
url = relative_url_for('blog_post', slug='flask-guide')
128
print(url) # Output: ../flask-guide/
129
130
# From /blog/python-tips/ to homepage
131
url = relative_url_for('index')
132
print(url) # Output: ../../
133
```
134
135
### URL Generation in Templates
136
137
When `FREEZER_RELATIVE_URLS` is enabled, templates automatically use `relative_url_for`:
138
139
```html
140
<!-- In Jinja2 template -->
141
<a href="{{ url_for('blog_post', slug='python-tips') }}">Python Tips</a>
142
<!-- Generates relative URL like: ../python-tips/ -->
143
144
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
145
<!-- Generates relative URL like: ../../static/css/style.css -->
146
```
147
148
### Custom Static File Discovery
149
150
```python
151
from flask_frozen import walk_directory
152
import os
153
154
def find_assets(asset_type):
155
"""Find assets of a specific type"""
156
extensions = {
157
'images': ['*.jpg', '*.png', '*.gif', '*.svg'],
158
'styles': ['*.css'],
159
'scripts': ['*.js'],
160
'fonts': ['*.woff', '*.woff2', '*.ttf', '*.eot'],
161
}
162
163
if asset_type not in extensions:
164
return []
165
166
# Ignore everything except the desired asset type
167
ignore_patterns = []
168
for other_type, exts in extensions.items():
169
if other_type != asset_type:
170
ignore_patterns.extend(exts)
171
172
assets = []
173
for filepath in walk_directory('static', ignore=ignore_patterns):
174
assets.append(filepath)
175
176
return assets
177
178
# Find all images
179
images = find_assets('images')
180
print(f"Found {len(images)} images")
181
```
182
183
### Building File Manifests
184
185
```python
186
from flask_frozen import walk_directory
187
import json
188
import hashlib
189
from pathlib import Path
190
191
def create_asset_manifest(static_dir):
192
"""Create a manifest of all static files with hashes"""
193
manifest = {}
194
195
for filepath in walk_directory(static_dir):
196
full_path = Path(static_dir) / filepath
197
198
# Calculate file hash for cache busting
199
with open(full_path, 'rb') as f:
200
file_hash = hashlib.md5(f.read()).hexdigest()[:8]
201
202
# Add to manifest
203
name, ext = filepath.rsplit('.', 1)
204
hashed_name = f"{name}.{file_hash}.{ext}"
205
206
manifest[filepath] = {
207
'hashed': hashed_name,
208
'size': full_path.stat().st_size,
209
'hash': file_hash,
210
}
211
212
return manifest
213
214
# Create and save manifest
215
manifest = create_asset_manifest('static')
216
with open('build/assets-manifest.json', 'w') as f:
217
json.dump(manifest, f, indent=2)
218
```
219
220
### Directory Synchronization
221
222
```python
223
from flask_frozen import walk_directory
224
import shutil
225
from pathlib import Path
226
227
def sync_directories(source, target, ignore_patterns=None):
228
"""Sync directories with ignore patterns"""
229
if ignore_patterns is None:
230
ignore_patterns = []
231
232
source_path = Path(source)
233
target_path = Path(target)
234
target_path.mkdir(parents=True, exist_ok=True)
235
236
# Copy files that match criteria
237
for filepath in walk_directory(source, ignore=ignore_patterns):
238
src_file = source_path / filepath
239
dst_file = target_path / filepath
240
241
# Create directory if needed
242
dst_file.parent.mkdir(parents=True, exist_ok=True)
243
244
# Copy file
245
shutil.copy2(src_file, dst_file)
246
print(f"Copied: {filepath}")
247
248
# Example usage
249
sync_directories(
250
'assets',
251
'build/assets',
252
ignore_patterns=['*.scss', '*/src/*', '.DS_Store']
253
)
254
```
255
256
## Integration with Freezer
257
258
These utilities integrate seamlessly with the Freezer class:
259
260
```python
261
from flask import Flask
262
from flask_frozen import Freezer, walk_directory
263
264
app = Flask(__name__)
265
freezer = Freezer(app)
266
267
# Custom static file generator using walk_directory
268
@freezer.register_generator
269
def custom_assets():
270
asset_dirs = ['media', 'uploads', 'downloads']
271
for asset_dir in asset_dirs:
272
if Path(asset_dir).exists():
273
for filepath in walk_directory(asset_dir):
274
# Generate URL for each asset file
275
yield f'/{asset_dir}/{filepath}'
276
277
# Use relative URLs in frozen site
278
app.config['FREEZER_RELATIVE_URLS'] = True
279
280
freezer.freeze()
281
```