dahlia / sqlalchemy-imageattach

SQLAlchemy extension for attaching images to entities.
https://sqlalchemy-imageattach.readthedocs.io/
MIT License
116 stars 25 forks source link

Question about generating thumbnails (there is no original image yet) #35

Closed jpmn closed 6 years ago

jpmn commented 8 years ago

I have a list of images and I would like to insert a Sample entity in the database for each of them.

import uuid

from sqlalchemy import Column, ForeignKey, Unicode
from sqlalchemy.dialects.postgresql import UUID

from sqlalchemy.orm import relationship
from sqlalchemy_imageattach.entity import Image, image_attachment

from .meta import Base

class Sample(Base):
    __tablename__ = 'sample'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(Unicode)
    picture_id = Column(UUID(as_uuid=True), ForeignKey('picture.id'), nullable=True)
    picture = image_attachment('Picture', foreign_keys=picture_id, single_parent=True)

class Picture(Base, Image):
    __tablename__ = 'picture'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True)
    name = Column(Unicode)

    @property
    def object_id(self):
        return self.id.int

Here is my conftest.py for py.test. I have a fixture that creates the store for the whole testing session, and I have another fixture to create a new SQLAlchemy session for each test.

import pytest

from sqlalchemy import create_engine
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy_imageattach.stores.fs import FileSystemStore

from databasset.models.meta import Base
from databasset import models

DB_URL = 'postgresql://devuser:devuser@localhost:5432/devdatabase'

def database_drop_all(engine):
    from sqlalchemy.engine import reflection
    from sqlalchemy.schema import (
        MetaData,
        Table,
        DropTable,
        ForeignKeyConstraint,
        DropConstraint)

    conn = engine.connect()
    trans = conn.begin()

    inspector = reflection.Inspector.from_engine(engine)

    metadata = MetaData()

    tbs = []
    all_fks = []

    for table_name in inspector.get_table_names():
        fks = []
        for fk in inspector.get_foreign_keys(table_name):
            if not fk['name']:
                continue
            fks.append(ForeignKeyConstraint((), (), name=fk['name']))
        t = Table(table_name, metadata, *fks)
        tbs.append(t)
        all_fks.extend(fks)

    for fkc in all_fks:
        conn.execute(DropConstraint(fkc))

    for table in tbs:
        conn.execute(DropTable(table))

    trans.commit()

def pytest_addoption(parser):
    parser.addoption('--db-url', action='store', default=DB_URL, help='Set the database engine URL')
    parser.addoption('--reset-db', action='store_true', default=False, help='Reset the database content')
    parser.addoption('--fill-db', action='store_true', default=False, help='Fill the database with content')

@pytest.fixture(scope='session')
def engine(request):
    """Session-wide test database."""
    _engine = create_engine(request.config.option.db_url)

    maker = sessionmaker()
    maker.configure(bind=_engine)

    Base.metadata.bind = _engine

    if request.config.option.reset_db:
        Base.metadata.reflect()
        Base.metadata.drop_all()

    Base.metadata.create_all()

    if request.config.option.fill_db:
        _session = maker()
        _session.commit()
        _session.close()

    return _engine

@pytest.yield_fixture(scope='function')
def session(request, engine):
    """Creates a new database session for a test."""
    connection = engine.connect()
    transaction = connection.begin()

    maker = sessionmaker()
    _session = maker(bind=connection)

    yield _session

    transaction.rollback()
    connection.close()
    _session.close()

@pytest.fixture(scope='session')
def store(request):
    _store = FileSystemStore(
        path='/path/to/the/store/images',
        base_url='http://localhost:5000/',
    )
    return _store

Here is my test_example.py which loops over a list of images and tries to insert them in a Sample. Then, I try to create several thumbnails before I commit the session.

import os
from sqlalchemy_imageattach.context import store_context
from databasset.models.sample import Sample

BASE_PATH = os.path.dirname(__file__)
IMG_PATHS = [
    os.path.join(BASE_PATH, 'assets/{}.jpg'.format(i)) for i in range(0, 10)
]

def test_example(session, store):
    with store_context(store):
        for i, img_path in enumerate(IMG_PATHS):
            sample = Sample(name='test image of {}'.format(i))

            with open(img_path, 'rb') as f:
                sample.picture.from_file(f)

            session.add(sample)
            session.flush()
            print(sample.picture.locate())

        samples = session.query(Sample).all()

        for item in samples:
            for width in [100, 200, 300, 400, 500]:
                image = item.picture.generate_thumbnail(width=width)
                session.flush()
                print(image.locate())

        session.commit()

The test fails in generate_thumbnail with the following error message: OSError: there is no original image yet. The original images are all in the folder hierarchy of the store.

What would be the proper way of doing this?

On a side note, I am not sure why I have to specify unique=True on the primary key of the Picture model. I thought a primary key was already a unique constraint. It throws this error without it:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) there is no unique constraint matching given keys for referenced table "picture"
[SQL: '\nCREATE TABLE diamond (\n\tid UUID NOT NULL, \n\tname VARCHAR, \n\tpicture_id UUID, \n\tCONSTRAINT pk_diamond PRIMARY KEY (id), \n\tCONSTRAINT fk_diamond_picture_id_picture FOREIGN KEY(picture_id) REFERENCES picture (id)\n)\n\n']
jpmn commented 8 years ago

It seems that the first thumbnail (width=100) is created for the first sample. Then it crashes on the second iteration (width=200) with the error: OSError: there is no original image yet.

If the thumbnail loop contains only one element, then it works for the first thumbnail of all samples. Why is it not possible to generate multiple thumbnails of an image in a row?