GitHub Cache

Cache dependencies and build outputs across workflow runs for faster execution.

Overview

The GitHubCache class provides a typed interface for GitHub Actions cache operations. It enables performance optimizations through caching across workflow runs, supporting hierarchical key fallbacks and cross-job data sharing.

API Reference

GitHubCache(github_token=None, github_repo=None)

Initializes the cache client for GitHub Actions cache operations.

Both parameters are optional but environment variables for them need to be present: GITHUB_TOKEN and GITHUB_REPOSITORY respectively.

example:

>> from github_action_toolkit import GitHubCache
>> cache = GitHubCache()

GitHubCache.save_cache(paths, key, enable_cross_os_archive=False)

Save cache with the specified key. Supports composite keys for fine-grained cache management.

  • paths: List of file/directory paths to cache (list of strings or Path objects)

  • key: Primary cache key (can include variables like version, hash, platform)

  • enable_cross_os_archive (optional): Enable cross-OS compatibility (default: False)

Returns the cache ID if successful, None if cache already exists.

example:

>> import hashlib
>> from pathlib import Path
>>
>> # Calculate hash of lockfile for cache key
>> lockfile = Path("package-lock.json")
>> lockfile_hash = hashlib.sha256(lockfile.read_bytes()).hexdigest()[:8]
>>
>> # Create composite key with platform and hash
>> import platform
>> cache_key = f"npm-{platform.system()}-{lockfile_hash}"
>>
>> # Save node_modules to cache
>> paths = ["node_modules", ".npm"]
>> cache_id = cache.save_cache(paths, cache_key)
>> if cache_id:
>>     print(f"Cache saved with ID: {cache_id}")
>> else:
>>     print("Cache already exists")

# Output:
# Cache saved with ID: 123456

GitHubCache.restore_cache(paths, primary_key, restore_keys=None, enable_cross_os_archive=False)

Restore cache with fallback key hierarchy. Tries the primary key first, then falls back to restore keys in order.

  • paths: List of file/directory paths to restore to (list of strings or Path objects)

  • primary_key: Primary cache key to look for (exact match)

  • restore_keys (optional): List of fallback keys to try if primary not found (supports prefix matching)

  • enable_cross_os_archive (optional): Enable cross-OS compatibility (default: False)

Returns the matched cache key if found and restored, None if no cache found.

example:

>> import hashlib
>> import platform
>> from pathlib import Path
>>
>> # Calculate hash for primary key
>> lockfile_hash = hashlib.sha256(Path("package-lock.json").read_bytes()).hexdigest()[:8]
>> system = platform.system()
>>
>> # Define primary and fallback keys
>> primary_key = f"npm-{system}-{lockfile_hash}"
>> restore_keys = [
>>     f"npm-{system}-",  # Any cache for this OS
>>     "npm-",            # Any npm cache
>> ]
>>
>> # Try to restore cache
>> matched_key = cache.restore_cache(["node_modules"], primary_key, restore_keys)
>> if matched_key:
>>     print(f"Cache restored from key: {matched_key}")
>> else:
>>     print("No cache found")

# Output:
# Cache restored from key: npm-Linux-abc12345

GitHubCache.is_feature_available()

Check if the cache feature is available in the current environment. Useful for gracefully handling cases where cache is not available.

Returns True if cache is available, False otherwise.

example:

>> if cache.is_feature_available():
>>     print("Cache is available")
>>     cache.save_cache(paths, key)
>> else:
>>     print("Cache is not available, skipping")

# Output:
# Cache is available

Common Usage Patterns

Caching Dependencies

from github_action_toolkit import GitHubCache
import hashlib
import platform
from pathlib import Path

cache = GitHubCache()

# Generate cache key from lockfile
lockfile = Path("requirements.txt")
lockfile_hash = hashlib.sha256(lockfile.read_bytes()).hexdigest()[:8]
cache_key = f"python-{platform.system()}-{lockfile_hash}"

# Try to restore cache
restore_keys = [f"python-{platform.system()}-"]
matched_key = cache.restore_cache([".venv"], cache_key, restore_keys)

if matched_key:
    print(f"Dependencies restored from cache: {matched_key}")
else:
    print("No cache found, installing dependencies...")
    # Install dependencies here
    
    # Save to cache for next time
    cache_id = cache.save_cache([".venv"], cache_key)
    if cache_id:
        print(f"Dependencies cached with ID: {cache_id}")

Cross-Job Data Passing

# Job 1: Build and cache artifacts
from github_action_toolkit import GitHubCache

cache = GitHubCache()
build_key = f"build-{os.environ['GITHUB_SHA']}"

# Build your project
# ... build process ...

# Cache build outputs
cache.save_cache(["dist", "build"], build_key)

# Job 2: Use cached artifacts
from github_action_toolkit import GitHubCache

cache = GitHubCache()
build_key = f"build-{os.environ['GITHUB_SHA']}"

# Restore build outputs from Job 1
matched_key = cache.restore_cache(["dist", "build"], build_key)
if matched_key:
    print("Build artifacts restored")
    # Use artifacts for deployment/testing

Exception Handling

The cache module provides specific exceptions for different error scenarios:

from github_action_toolkit import (
    GitHubCache,
    CacheNotFoundError,
    CacheRestoreError,
    CacheSaveError
)

cache = GitHubCache()

try:
    cache.save_cache(["node_modules"], "my-cache-key")
except CacheSaveError as e:
    print(f"Failed to save cache: {e}")

try:
    cache.restore_cache(["node_modules"], "my-cache-key")
except CacheRestoreError as e:
    print(f"Failed to restore cache: {e}")

Cache Key Best Practices

  1. Include platform information for OS-specific dependencies:

    key = f"deps-{platform.system()}-{hash}"
    
  2. Use file hashes for exact dependency matching:

    hash = hashlib.sha256(Path("package-lock.json").read_bytes()).hexdigest()[:8]
    
  3. Create fallback hierarchy for better cache hit rates:

    primary = f"npm-Linux-abc12345"
    fallbacks = ["npm-Linux-", "npm-"]
    
  4. Keep keys under 512 characters to comply with GitHub’s limits.

  5. Use semantic versioning in keys when appropriate:

    key = f"build-v1.2.3-{platform.system()}"
    

Caching Strategies

Dependency Caching

from pathlib import Path
from github_action_toolkit import GitHubCache, info

def cache_dependencies(requirements_file: Path):
    """Cache Python dependencies."""
    cache = GitHubCache()
    
    # Generate cache key from requirements hash
    key = f"python-deps-{hash_file(requirements_file)}"
    restore_keys = ['python-deps-']  # Fallback to any python deps
    
    # Try to restore
    cache_hit = cache.restore_cache(
        paths=['.venv'],
        key=key,
        restore_keys=restore_keys
    )
    
    if cache_hit:
        info(f'Cache hit: {key}')
        return True
    
    info('Cache miss, installing dependencies...')
    # Install dependencies
    install_dependencies()
    
    # Save for next time
    cache.save_cache(paths=['.venv'], key=key)
    return False

def hash_file(path: Path) -> str:
    """Generate hash of file contents."""
    import hashlib
    return hashlib.sha256(path.read_bytes()).hexdigest()[:8]

Build Output Caching

from github_action_toolkit import GitHubCache, info

def cache_build_artifacts():
    """Cache build outputs."""
    cache = GitHubCache()
    
    # Include environment in key for cross-OS safety
    import platform
    os_name = platform.system().lower()
    
    key = f'build-{os_name}-{get_git_commit_hash()}'
    
    try:
        if cache.restore_cache(paths=['dist', 'build'], key=key):
            info('Using cached build artifacts')
            return True
    except Exception as e:
        info(f'Cache restore failed: {e}')
    
    return False