Image & Thumbnails¶
This is how to attach image file to sqlalchemy model using the FileSystemStore
.
0. Prerequisites¶
$ pip install sqlalchemy-media
1. Creating workbench¶
Setting up and engine
along-side the session_factory
. And creating a constant for the directory to store files.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
TEMP_PATH = '/tmp/sqlalchemy-media'
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=False)
session_factory = sessionmaker(bind=engine)
2. Storage¶
Registering a default factory for FileSystemStore
class. the StoreManager
will call it when needed.
import functools
from sqlalchemy_media import StoreManager, FileSystemStore
StoreManager.register(
'fs',
functools.partial(FileSystemStore, TEMP_PATH, 'http://static.example.org/'),
default=True
)
3. Backend Type¶
We are using the sqlite’s memory db in this tutorial, because it’s so handy. but it’s not supporting the JSON data type, So, this is how to emulate a type using the SqlAlchemy Type Decorators.
import json
from sqlalchemy import TypeDecorator, Unicode
class Json(TypeDecorator):
impl = Unicode
def process_bind_param(self, value, engine):
return json.dumps(value)
def process_result_value(self, value, engine):
if value is None:
return None
return json.loads(value)
Note
You can use any type to store dictionary and list as described on top, but the postgreSql HStore
and
JSON
are preferred.
4. Defining The Model¶
As described in
Sqlalchemy’s documentation,
the as_mutable
method is used to make a type mutable.
from sqlalchemy import Column, Integer
from sqlalchemy_media import Image, ImageAnalyzer, ImageValidator, ImageProcessor
class ProfileImage(Image):
__pre_processors__ = [
ImageAnalyzer(),
ImageValidator(
minimum=(80, 80),
maximum=(800, 600),
min_aspect_ratio=1.2,
content_types=['image/jpeg', 'image/png']
),
ImageProcessor(
fmt='jpeg',
width=120,
crop=dict(
left='10%',
top='10%',
width='80%',
height='80%',
)
)
]
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(Unicode(100))
image = Column(ProfileImage.as_mutable(Json))
def __repr__(self):
return "<%s id=%s>" % (self.name, self.id)
5. DB Schema¶
Making database objects using the famous function create_all
, and creating a session instance to interact with
database.
Base.metadata.create_all(engine, checkfirst=True)
session = session_factory()
6. Action !¶
with StoreManager(session):
person1 = Person()
person1.image = ProfileImage.create_from('https://www.python.org/static/img/[email protected]')
session.add(person1)
session.commit()
print('Content type:', person1.image.content_type)
print('Extension:', person1.image.extension)
print('Length:', person1.image.length)
print('Original filename:', person1.image.original_filename)
Content type: image/jpeg
Extension: .jpg
Length: 2020
Original filename: https://www.python.org/static/img/[email protected]
7. Thumbnails¶
from os.path import exists, join
with StoreManager(session):
thumbnail = person1.image.get_thumbnail(width=32, auto_generate=True)
print(thumbnail.height)
assert exists(join(TEMP_PATH, thumbnail.path))
The thumbnail height is:
8
Note
If your delete an image using instructions bellow, all thumbnails will be deleted also.
Warning
Remember to commit the sqlalchemy’s session
after thumbnail generation to store the info, it’s also
can rollbacks the operation if transaction failed.
Generating thumbnail with ratio
from os.path import exists, join
with StoreManager(session):
thumbnail = person1.image.get_thumbnail(ratio=.3, auto_generate=True)
print(thumbnail.width, thumbnail.height)
assert exists(join(TEMP_PATH, thumbnail.path))
28 7
Call person1.image.locate()
or person1.image.get_thumbnail(width=32, auto_generate=True).locate()
to get the
files URL in store.
8. Overwriting¶
Overwriting a file is achieved by attaching an image by attach()
with StoreManager(session):
person1.image.attach('https://www.python.org/static/img/python-logo.png')
session.commit()
print('Content type:', person1.image.content_type)
print('Extension:', person1.image.extension)
print('Length:', person1.image.length)
print('Original filename:', person1.image.original_filename)
Content type: image/jpeg
Extension: .jpg
Length: 2080
Original filename: https://www.python.org/static/img/python-logo.png
9. Deleting & Delete Orphan¶
Set the model’s attribute to None
, while delete_orphan=True
passed to StoreManager
.
with StoreManager(session, delete_orphan=True):
deleted_filename = join(TEMP_PATH, person1.image.path)
person1.image = None
session.commit()
assert not exists(deleted_filename)
Another way is to re-set the attribute using new instance:
with StoreManager(session, delete_orphan=True):
person1.image = ProfileImage.create_from('https://www.python.org/static/img/python-logo.png')
session.commit()
print('Content type:', person1.image.content_type)
print('Extension:', person1.image.extension)
print('Length:', person1.image.length)
print('Original filename:', person1.image.original_filename)
Content type: image/jpeg
Extension: .jpg
Length: 2080
Original filename: https://www.python.org/static/img/python-logo.png
Warning
Setting SqlAlchemy model’s attribute without enabling delete_orphan
will cause the orphaned files
remaining in store for ever.