Migration Guide
Learn how to migrate from other file storage libraries to file-keeper.
Migrating from Direct Filesystem Operations
If you're currently using direct filesystem operations, here's how to migrate to file-keeper's filesystem adapter.
Before: Direct Filesystem Operations
import os
import shutil
from pathlib import Path
# Creating directories
upload_dir = Path("/uploads")
upload_dir.mkdir(parents=True, exist_ok=True)
# Saving a file
file_path = upload_dir / "myfile.txt"
with open(file_path, 'wb') as f:
f.write(b"file content")
# Reading a file
with open(file_path, 'rb') as f:
content = f.read()
# Checking if file exists
if file_path.exists():
print("File exists")
# Removing a file
if file_path.exists():
file_path.unlink()
After: Using file-keeper
import file_keeper as fk
# Initialize storage
storage = fk.make_storage("fs", {
"type": "file_keeper:fs",
"path": "/uploads",
"initialize": True # Creates directory if it doesn't exist
})
# Uploading a file
upload = fk.make_upload(b"file content")
file_info = storage.upload("myfile.txt", upload)
# Reading a file
content = storage.content(file_info)
# Checking if file exists
if storage.supports(fk.Capability.EXISTS):
exists = storage.exists(file_info)
print(f"File exists: {exists}")
# Removing a file
if storage.supports(fk.Capability.REMOVE):
storage.remove(file_info)
Benefits of Migration
- Consistency: Same API regardless of storage backend
- Security: Built-in protection against directory traversal
- Flexibility: Easy switch to cloud storage later
- Capabilities: Runtime checking of supported operations
Migrating from boto3 (AWS S3)
Before: Using boto3 directly
import boto3
from botocore.exceptions import ClientError
# Initialize client
s3_client = boto3.client(
's3',
aws_access_key_id='your-key',
aws_secret_access_key='your-secret',
region_name='us-east-1'
)
bucket_name = 'my-bucket'
# Upload file
def upload_file(key, file_content):
s3_client.put_object(
Bucket=bucket_name,
Key=key,
Body=file_content
)
# Download file
def download_file(key):
try:
response = s3_client.get_object(Bucket=bucket_name, Key=key)
return response['Body'].read()
except ClientError as e:
if e.response['Error']['Code'] == 'NoSuchKey':
return None
raise
# Check if file exists
def file_exists(key):
try:
s3_client.head_object(Bucket=bucket_name, Key=key)
return True
except ClientError as e:
if e.response['Error']['Code'] == '404':
return False
raise
# Delete file
def delete_file(key):
s3_client.delete_object(Bucket=bucket_name, Key=key)
After: Using file-keeper S3 adapter
import file_keeper as fk
from file_keeper import exc
# Initialize storage
storage = fk.make_storage("s3", {
"type": "file_keeper:s3",
"bucket": "my-bucket",
"key": "your-key",
"secret": "your-secret",
"region": "us-east-1"
})
# Upload file
upload = fk.make_upload(b"file content")
file_info = storage.upload("myfile.txt", upload)
# Download file
try:
content = storage.content(file_info)
except exc.MissingFileError:
content = None
# Check if file exists
if storage.supports(fk.Capability.EXISTS):
exists = storage.exists(file_info)
# Delete file
if storage.supports(fk.Capability.REMOVE):
storage.remove(file_info)
Benefits of Migration
- Simplified Error Handling: Unified exception hierarchy
- Capability Checking: Know what operations are supported
- Backend Flexibility: Can switch to local storage for testing
- Consistent API: Same interface as other storage types
Migrating from Django Storages
Before: Using Django Storages
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
# Save file
def save_file(filename, content):
path = default_storage.save(filename, ContentFile(content))
return path
# Read file
def read_file(filename):
if default_storage.exists(filename):
with default_storage.open(filename, 'rb') as f:
return f.read()
return None
# Get file URL
def get_file_url(filename):
if default_storage.exists(filename):
return default_storage.url(filename)
return None
# Delete file
def delete_file(filename):
if default_storage.exists(filename):
default_storage.delete(filename)
After: Using file-keeper with Django
import file_keeper as fk
from file_keeper import exc
# Initialize storage (could be configured via Django settings)
storage = fk.make_storage("django_files", {
"type": "file_keeper:fs", # or s3, gcs, etc.
"path": "/path/to/django/media",
"initialize": True
})
def save_file(filename, content):
upload = fk.make_upload(content)
file_info = storage.upload(filename, upload)
return file_info.location
def read_file(filename):
# Find file by scanning or you'd need to store file_info elsewhere
for loc in storage.scan():
if loc == filename:
try:
file_info = storage.analyze(loc)
return storage.content(file_info)
except exc.FilesError:
return None
return None
def delete_file(filename):
# Find file by scanning
for loc in storage.scan():
if loc == filename:
try:
file_info = storage.analyze(loc)
if storage.supports(fk.Capability.REMOVE):
return storage.remove(file_info)
except exc.FilesError:
return False
return False
Migrating from Google Cloud Storage (gcs)
Before: Using google-cloud-storage directly
from google.cloud import storage
from google.cloud.exceptions import NotFound
# Initialize client
client = storage.Client()
bucket = client.bucket('my-bucket')
# Upload file
def upload_file(blob_name, file_content):
blob = bucket.blob(blob_name)
blob.upload_from_string(file_content)
# Download file
def download_file(blob_name):
blob = bucket.blob(blob_name)
try:
return blob.download_as_bytes()
except NotFound:
return None
# Check if file exists
def file_exists(blob_name):
blob = bucket.blob(blob_name)
return blob.exists()
# Delete file
def delete_file(blob_name):
blob = bucket.blob(blob_name)
blob.delete()
After: Using file-keeper GCS adapter
import file_keeper as fk
from file_keeper import exc
# Initialize storage
storage = fk.make_storage("gcs", {
"type": "file_keeper:gcs",
"bucket_name": "my-bucket",
"credentials_file": "/path/to/credentials.json" # or use other auth methods
})
# Upload file
upload = fk.make_upload(b"file content")
file_info = storage.upload("myfile.txt", upload)
# Download file
try:
content = storage.content(file_info)
except exc.MissingFileError:
content = None
# Check if file exists
if storage.supports(fk.Capability.EXISTS):
exists = storage.exists(file_info)
# Delete file
if storage.supports(fk.Capability.REMOVE):
storage.remove(file_info)
Migration Strategy
1. Gradual Migration Approach
Start by replacing one storage operation at a time:
# Old code
import os
def save_user_avatar_old(user_id, avatar_data):
filepath = f"/avatars/{user_id}.jpg"
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'wb') as f:
f.write(avatar_data)
return filepath
# New code
import file_keeper as fk
storage = fk.make_storage("avatars", {
"type": "file_keeper:fs",
"path": "/avatars",
"initialize": True
})
def save_user_avatar_new(user_id, avatar_data):
upload = fk.make_upload(avatar_data)
file_info = storage.upload(f"{user_id}.jpg", upload)
return file_info.location
2. Configuration Management
Create a configuration factory to make switching between environments easier:
import os
import file_keeper as fk
def create_file_storage(env=None):
"""Create appropriate storage based on environment."""
env = env or os.getenv('ENVIRONMENT', 'development')
if env == 'production':
return fk.make_storage("prod", {
"type": "file_keeper:s3",
"bucket": os.getenv('S3_BUCKET'),
"key": os.getenv('AWS_ACCESS_KEY_ID'),
"secret": os.getenv('AWS_SECRET_ACCESS_KEY'),
"region": os.getenv('AWS_DEFAULT_REGION', 'us-east-1')
})
else:
# Use local storage for development/testing
return fk.make_storage("dev", {
"type": "file_keeper:fs",
"path": "./dev-storage",
"initialize": True
})
# Use the factory
storage = create_file_storage()
3. Testing During Migration
Maintain both old and new implementations temporarily and compare results:
import tempfile
import os
def test_migration_consistency():
"""Test that old and new implementations produce same results."""
# Old implementation
with tempfile.TemporaryDirectory() as tmpdir:
old_filepath = os.path.join(tmpdir, "test.txt")
with open(old_filepath, 'wb') as f:
f.write(b"test content")
with open(old_filepath, 'rb') as f:
old_content = f.read()
# New implementation
storage = fk.make_storage("test", {
"type": "file_keeper:fs",
"path": tmpdir,
"initialize": True
})
upload = fk.make_upload(b"test content")
file_info = storage.upload("test.txt", upload)
new_content = storage.content(file_info)
# Compare
assert old_content == new_content
print("Migration test passed!")
Common Migration Pitfalls to Avoid
- Don't forget to check capabilities - Always verify that operations are supported before calling them
- Handle exceptions properly - Use file-keeper's exception hierarchy
- Update your tests - Mock file-keeper storage instead of filesystem operations
- Consider data migration - Plan how to migrate existing files to new storage
- Update security policies - Ensure new storage backend has appropriate access controls
By following these migration patterns, you can gradually transition your application to use file-keeper while maintaining functionality and gaining the benefits of a unified storage interface.