File Handling in Panther#
Panther provides file handling capabilities through the File
and Image
classes for file uploads and processing.
Overview#
File
: General-purpose file handling for any type of fileImage
: Specialized file handling for images with automatic MIME type validation
Basic Usage#
Importing File Classes#
from panther.file_handler import File, Image
Creating File Objects#
# Create from bytes (in-memory file)
file = File(
file_name='document.pdf',
content_type='application/pdf',
file=b'PDF content here...'
)
# Create image file
image = Image(
file_name='photo.jpg',
content_type='image/jpeg',
file=b'JPEG content here...'
)
File Properties#
Property | Type | Description |
---|---|---|
file_name |
str | The name of the file |
content_type |
str | The MIME type of the file |
file |
bytes | None | The file content in bytes |
size |
int | File size in bytes |
File Methods#
# Read file content
content = file.read()
# Save file to disk
path = file.save("uploads/")
# Use as context manager
with file as f:
content = f.read()
Saving Files#
The save()
method provides flexible file saving with several features:
Basic Saving#
# Save with original filename
path = file.save()
# Save with custom filename
path = file.save("custom_name.txt")
# Save with overwrite
path = file.save("existing_file.txt", overwrite=True)
Directory Path Handling#
When you provide a path ending with /
, it's treated as a directory:
# Save to directory with original filename
path = file.save("uploads/") # Saves as "uploads/original_filename.ext"
# Save to nested directory
path = file.save("uploads/images/") # Saves as "uploads/images/original_filename.ext"
# Save to directory with custom filename
path = file.save("uploads/custom_name.txt") # Saves as "uploads/custom_name.txt"
Idempotent Behavior#
The save()
method is idempotent - calling it multiple times on the same file instance returns the same path:
file = File(file_name='test.txt', content_type='text/plain', file=b'content')
# First call - creates file and returns path
path1 = file.save("uploads/")
print(path1) # "uploads/test.txt"
# Subsequent calls - returns same path without creating new files
path2 = file.save("uploads/")
path3 = file.save("uploads/")
assert path1 == path2 == path3 # All return the same path
This prevents accidental creation of duplicate files with different timestamps.
Automatic Directory Creation#
The save()
method automatically creates directories if they don't exist:
# This will create the "uploads/images" directory structure if it doesn't exist
path = file.save("uploads/images/")
Integration with Models#
Files can be used as model attributes:
from datetime import datetime
from panther.db import Model
from panther.file_handler import File, Image
class Document(Model):
title: str
file: File
uploaded_at: datetime
class Profile(Model):
name: str
avatar: Image
bio: str | None = None
When using File types in models: - The file metadata (name, content type) is preserved - The file path is stored in the database - The actual file content is automatically saved to disk when inserting the model - You can access file properties and methods on the model instance
Integration with Serializers#
from panther.serializer import ModelSerializer
from panther.file_handler import File, Image
class FileUploadSerializer(ModelSerializer):
class Config:
model = FileUpload
fields = ['file', 'description']
required_fields = ['file']
class ImageUploadSerializer(ModelSerializer):
class Config:
model = ImageUpload
fields = ['image', 'title']
required_fields = ['image']
File Validation#
from pydantic import field_validator
from panther import status
from panther.exceptions import APIError
from panther.file_handler import File
from panther.serializer import ModelSerializer
class DocumentUploadSerializer(ModelSerializer):
class Config:
model = DocumentUpload
fields = ['file', 'title']
required_fields = ['file']
@field_validator('file')
@classmethod
def validate_file_size(cls, file: File) -> File:
if file.size > 10 * 1024 * 1024: # 10MB limit
raise APIError(
detail='File size must be less than 10MB',
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
return file
API Integration#
File Upload API#
from panther.app import API
from panther.request import Request
from panther.response import Response
@API(input_model=FileUploadSerializer)
async def upload_file(request: Request):
file_data = request.validated_data
file = file_data.file
# Save file to disk
saved_path = file.save("uploads/")
# Store in database
document = await Document.insert_one({
'title': file_data.description,
'file': file,
'uploaded_at': datetime.now()
})
return Response(data={
"message": "File uploaded successfully",
"saved_path": saved_path
})
Error Handling#
from panther import status
from panther.exceptions import APIError
@API(input_model=FileUploadSerializer)
async def upload_file(request: Request):
try:
file_data = request.validated_data
file = file_data.file
# Check file size
if file.size > 10 * 1024 * 1024: # 10MB limit
raise APIError(
detail="File too large. Maximum size is 10MB.",
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
saved_path = file.save("uploads/")
return Response(data={"saved_path": saved_path})
except Exception as e:
raise APIError(
detail=f"File upload failed: {str(e)}",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
Best Practices#
- Validate file types: Always check MIME types to prevent malicious uploads
- Limit file sizes: Set reasonable size limits to prevent abuse
- Use context managers: When reading files, use
with
statements for proper cleanup - Handle errors gracefully: Provide clear error messages for validation failures
- Secure file storage: Always validate and sanitize file names before saving
- Leverage idempotency: The
save()
method is idempotent, so you can call it multiple times safely - Use directory paths: When saving files, use directory paths ending with
/
for better organization
This guide covers the essential aspects of file handling in Panther. For more advanced features and detailed examples, refer to the API documentation and examples.