CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-frozen-flask

Freezes a Flask application into a set of static files.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

utilities.mddocs/

Utility Functions

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.

Capabilities

Directory Operations

Functions for traversing and processing directory structures.

def walk_directory(root, ignore=()):
    """
    Walk directory and yield slash-separated paths relative to root.
    
    Used to implement URL generator for static files and file discovery.
    
    Parameters:
    - root: Directory path to walk (str or Path)
    - ignore: List of fnmatch patterns to ignore
    
    Yields:
    str: Relative file paths with forward slashes
    
    Ignore patterns:
    - Patterns with slash are matched against full path
    - Patterns without slash match individual path components
    - Patterns ending with '/' match directories
    """

URL Generation

Functions for generating relative URLs and handling URL transformations.

def relative_url_for(endpoint, **values):
    """
    Like Flask's url_for but returns relative URLs when possible.
    
    Absolute URLs (with _external=True or different subdomain) are unchanged.
    Relative URLs are converted based on current request context path.
    URLs ending with '/' get 'index.html' appended for static compatibility.
    
    Parameters:
    - endpoint: Flask endpoint name
    - **values: URL parameters (same as url_for)
    
    Returns:
    str: Relative URL or absolute URL if necessary
    
    Note: Should only be used with Frozen-Flask, not in live Flask apps
    """

Usage Examples

Directory Walking

from flask_frozen import walk_directory
from pathlib import Path

# Basic directory traversal
static_dir = Path('static')
for filepath in walk_directory(static_dir):
    print(f"Found file: {filepath}")
    # Output: css/style.css, js/app.js, images/logo.png, etc.

Directory Walking with Ignore Patterns

# Ignore specific files and directories
ignore_patterns = [
    '*.pyc',           # Ignore Python bytecode
    '*.pyo',           # Ignore optimized bytecode
    '__pycache__/',    # Ignore Python cache directories
    'node_modules/',   # Ignore Node.js dependencies
    '*.scss',          # Ignore SCSS source files
    'src/*',           # Ignore entire src directory
    '.DS_Store',       # Ignore macOS system files
]

for filepath in walk_directory('assets', ignore=ignore_patterns):
    print(f"Will be included: {filepath}")

Complex Ignore Patterns

# Advanced ignore patterns
patterns = [
    # Pattern matching examples:
    '*.log',           # Matches any .log file
    'temp/',           # Matches temp directory
    'draft-*',         # Matches files starting with 'draft-'
    '*/cache/*',       # Matches cache directories anywhere
    'build/*.map',     # Matches .map files in build directory
    '.git*',           # Matches .git, .gitignore, etc.
]

# Custom file filtering
def custom_file_filter(root_dir):
    files = []
    for filepath in walk_directory(root_dir, ignore=patterns):
        # Additional custom filtering
        if not filepath.startswith('_'):  # Skip files starting with underscore
            files.append(filepath)
    return files

Relative URL Generation

from flask import Flask, request
from flask_frozen import relative_url_for

app = Flask(__name__)

@app.route('/blog/<slug>/')
def blog_post(slug):
    return f'Blog post: {slug}'

# In a template or view context
with app.test_request_context('/blog/python-tips/'):
    # From /blog/python-tips/ to /blog/flask-guide/
    url = relative_url_for('blog_post', slug='flask-guide')
    print(url)  # Output: ../flask-guide/
    
    # From /blog/python-tips/ to homepage
    url = relative_url_for('index')
    print(url)  # Output: ../../

URL Generation in Templates

When FREEZER_RELATIVE_URLS is enabled, templates automatically use relative_url_for:

<!-- In Jinja2 template -->
<a href="{{ url_for('blog_post', slug='python-tips') }}">Python Tips</a>
<!-- Generates relative URL like: ../python-tips/ -->

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- Generates relative URL like: ../../static/css/style.css -->

Custom Static File Discovery

from flask_frozen import walk_directory
import os

def find_assets(asset_type):
    """Find assets of a specific type"""
    extensions = {
        'images': ['*.jpg', '*.png', '*.gif', '*.svg'],
        'styles': ['*.css'],
        'scripts': ['*.js'],
        'fonts': ['*.woff', '*.woff2', '*.ttf', '*.eot'],
    }
    
    if asset_type not in extensions:
        return []
    
    # Ignore everything except the desired asset type
    ignore_patterns = []
    for other_type, exts in extensions.items():
        if other_type != asset_type:
            ignore_patterns.extend(exts)
    
    assets = []
    for filepath in walk_directory('static', ignore=ignore_patterns):
        assets.append(filepath)
    
    return assets

# Find all images
images = find_assets('images')
print(f"Found {len(images)} images")

Building File Manifests

from flask_frozen import walk_directory
import json
import hashlib
from pathlib import Path

def create_asset_manifest(static_dir):
    """Create a manifest of all static files with hashes"""
    manifest = {}
    
    for filepath in walk_directory(static_dir):
        full_path = Path(static_dir) / filepath
        
        # Calculate file hash for cache busting
        with open(full_path, 'rb') as f:
            file_hash = hashlib.md5(f.read()).hexdigest()[:8]
        
        # Add to manifest
        name, ext = filepath.rsplit('.', 1)
        hashed_name = f"{name}.{file_hash}.{ext}"
        
        manifest[filepath] = {
            'hashed': hashed_name,
            'size': full_path.stat().st_size,
            'hash': file_hash,
        }
    
    return manifest

# Create and save manifest
manifest = create_asset_manifest('static')
with open('build/assets-manifest.json', 'w') as f:
    json.dump(manifest, f, indent=2)

Directory Synchronization

from flask_frozen import walk_directory
import shutil
from pathlib import Path

def sync_directories(source, target, ignore_patterns=None):
    """Sync directories with ignore patterns"""
    if ignore_patterns is None:
        ignore_patterns = []
    
    source_path = Path(source)
    target_path = Path(target)
    target_path.mkdir(parents=True, exist_ok=True)
    
    # Copy files that match criteria
    for filepath in walk_directory(source, ignore=ignore_patterns):
        src_file = source_path / filepath
        dst_file = target_path / filepath
        
        # Create directory if needed
        dst_file.parent.mkdir(parents=True, exist_ok=True)
        
        # Copy file
        shutil.copy2(src_file, dst_file)
        print(f"Copied: {filepath}")

# Example usage
sync_directories(
    'assets',
    'build/assets',
    ignore_patterns=['*.scss', '*/src/*', '.DS_Store']
)

Integration with Freezer

These utilities integrate seamlessly with the Freezer class:

from flask import Flask
from flask_frozen import Freezer, walk_directory

app = Flask(__name__)
freezer = Freezer(app)

# Custom static file generator using walk_directory
@freezer.register_generator
def custom_assets():
    asset_dirs = ['media', 'uploads', 'downloads']
    for asset_dir in asset_dirs:
        if Path(asset_dir).exists():
            for filepath in walk_directory(asset_dir):
                # Generate URL for each asset file
                yield f'/{asset_dir}/{filepath}'

# Use relative URLs in frozen site
app.config['FREEZER_RELATIVE_URLS'] = True

freezer.freeze()

Install with Tessl CLI

npx tessl i tessl/pypi-frozen-flask

docs

configuration.md

core-freezing.md

index.md

url-generation.md

utilities.md

tile.json