Media asset management for Python, with glue code for various web frameworks
Essential utility functions and classes that provide core infrastructure for webassets, including path operations, context managers, object resolution, registry management, and security features.
def hash_func(data):
"""
Create hash from arbitrary data using webassets cache system.
Args:
data: Data to hash (can be complex objects)
Returns:
str: MD5 hash string
"""
md5_constructor = hashlib.md5def common_path_prefix(paths, sep=os.path.sep):
"""
Find common directory path prefix from multiple paths.
Improved version of os.path.commonpath() that handles both
forward and backward slashes correctly.
Args:
paths: List of file paths
sep: Path separator to use in result (default: os.path.sep)
Returns:
str: Common directory prefix
"""
def is_url(s):
"""
Check if string is a valid URL.
Args:
s: String to check
Returns:
bool: True if string has valid URL scheme and netloc
"""@contextlib.contextmanager
def working_directory(directory=None, filename=None):
"""
Context manager to temporarily change working directory.
Useful for filters that need to run in specific directories.
Restores original directory on exit.
Args:
directory: Directory to change to
filename: File path (directory will be extracted)
Note: Exactly one of directory or filename must be provided.
Usage:
with working_directory('/path/to/dir'):
# commands run in /path/to/dir
# back to original directory
"""def make_option_resolver(clazz=None, attribute=None, classes=None,
allow_none=True, desc=None):
"""
Create a function that resolves options to objects.
The resolver can handle:
- Object instances (returned as-is)
- Class objects (instantiated)
- String identifiers (resolved from registry)
- Arguments in format "key:argument"
Args:
clazz: Base class for type checking
attribute: Duck-typing attribute to check for
classes: Registry dictionary for string resolution
allow_none: Whether None values are allowed
desc: Description for error messages
Returns:
function: Resolver function that takes (option, env=None)
"""def RegistryMetaclass(clazz=None, attribute=None, allow_none=True, desc=None):
"""
Create a metaclass that maintains a registry of subclasses.
Classes using this metaclass are automatically registered by their
'id' attribute and can be resolved from strings.
Args:
clazz: Base class for type checking
attribute: Duck-typing attribute
allow_none: Whether None resolution is allowed
desc: Description for error messages
Returns:
type: Metaclass with REGISTRY dict and resolve() method
Features:
- Automatic subclass registration by 'id' attribute
- String-to-class resolution via resolve() method
- Automatic __eq__, __str__, __unicode__ methods
- Integration with make_option_resolver
"""def cmp_debug_levels(level1, level2):
"""
Compare debug levels for filter execution.
Debug levels: False < 'merge' < True
Args:
level1: First debug level
level2: Second debug level
Returns:
int: -1 if level1 < level2, 0 if equal, 1 if level1 > level2
Raises:
BundleError: If invalid debug level provided
"""def calculate_sri(data):
"""
Calculate Subresource Integrity (SRI) hash for data.
Args:
data: Bytes data to hash
Returns:
str: SRI string in format 'sha384-<base64hash>'
"""
def calculate_sri_on_file(file_name):
"""
Calculate SRI hash for file contents.
Args:
file_name: Path to file
Returns:
str or None: SRI string, or None if file not found
"""# Compatibility imports for different Python versions
md5_constructor = hashlib.md5
set = set # Built-in set for older Python versions
StringIO = io.StringIO # String buffer for text operations
pickle = pickle # Serialization moduleThe option resolver system enables flexible configuration:
# String resolution with arguments
resolver = make_option_resolver(classes={'file': FileCache, 'mem': MemoryCache})
cache = resolver('file:/tmp/cache', env) # Creates FileCache('/tmp/cache')
# Class resolution with factory method
cache = resolver(FileCache, env) # Uses FileCache.make(env) if available
# Instance resolution
cache = resolver(existing_cache) # Returns existing_cache as-isThe metaclass provides automatic registration:
class BaseUpdater(metaclass=RegistryMetaclass(desc='updater')):
pass
class TimestampUpdater(BaseUpdater):
id = 'timestamp' # Automatically registered
# String resolution
updater = BaseUpdater.resolve('timestamp') # Returns TimestampUpdater instancePath utilities work consistently across operating systems:
# Handles mixed separators correctly
paths = ['/home/user/project\\assets\\css', '/home/user/project/assets/js']
common = common_path_prefix(paths) # '/home/user/project/assets'Context manager ensures directory restoration even on exceptions:
original_dir = os.getcwd()
with working_directory('/tmp/build'):
# Process files in /tmp/build
run_build_command()
# Exception here still restores directory
# Back in original_dirSRI support for Content Security Policy compliance:
# Generate SRI for inline content
content = "console.log('hello');"
sri = calculate_sri(content.encode('utf-8'))
# Use in HTML: <script integrity="sha384-...">
# Generate SRI for files
sri = calculate_sri_on_file('app.js')
if sri:
print(f'<script src="app.js" integrity="{sri}"></script>')class CustomCache:
@classmethod
def make(cls, env, path):
# Initialize with environment-specific settings
return cls(path, debug=env.debug)
resolver = make_option_resolver(classes={'custom': CustomCache})
cache = resolver('custom:/tmp/cache', env) # Uses make() methodclass FilterBase(metaclass=RegistryMetaclass(desc='filter')):
def apply(self, content):
raise NotImplementedError
class JSMinFilter(FilterBase):
id = 'jsmin'
def apply(self, content):
return minify_js(content)
# Automatic resolution
filter_obj = FilterBase.resolve('jsmin') # Gets JSMinFilter instance# Filter selection based on debug level
available_filters = [dev_filter, prod_filter, debug_filter]
current_level = 'merge'
active_filters = [f for f in available_filters
if cmp_debug_levels(current_level, f.max_debug_level) <= 0]Install with Tessl CLI
npx tessl i tessl/pypi-webassets