Skip to content

Generic assignment (Auto Coercion) #98

Open
@lelit

Description

@lelit

First of all I'm sorry about the title, couldn't figure out a better phrase... second, sorry again for the length of this!

My use case is the following: I have different SA classes containing different Image kinds (that is, with different constraints such as max_length or pre_processors), for example:

from sqlalchemy_media import Image, ImageProcessor, KB, WandAnalyzer

class IconImage(Image):
    __auto_coercion__ = True
    __max_length__ = 256*KB
    __pre_processors__ = (
        WandAnalyzer(),
        ImageValidator(
            minimum=(16, 16),
            maximum=(800, 800),
            min_aspect_ratio=0.9,
            max_aspect_ratio=1.1,
            content_types=('image/png', 'image/jpeg', 'image/gif')),
        ImageProcessor(fmt='png', width=80),        
    )

class ProfileImage(Image):
    __auto_coercion__ = True
    __max_length__ = 2048*KB
    __pre_processors__ = (
        WandAnalyzer(),
        ImageValidator(
            minimum=(80, 80),
            maximum=(3840, 3840),
            min_aspect_ratio=0.9,
            max_aspect_ratio=1.1,
            content_types=('image/png', 'image/jpeg', 'image/gif')),
        ImageProcessor(fmt='png', width=80),        
    )

class Person(Base):
    __tablename__ = 'persons'

    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)

class Tool(Base):
    __tablename__ = 'tools'

    id = Column(Integer, primary_key=True)
    name = Column(Unicode(100))
    image = Column(IconImage.as_mutable(Json))

    def __repr__(self):
        return "<%s id=%s>" % (self.name, self.id)

I have then a generic function that, given the class of an entity and basically a dictionary coming from a data entry form, creates a new instance and initialize its content, greatly simplified as this:

def create_instance(class_, data):
    instance = class_()
    for key, value in data.items():
        setattr(instance, key, value)

For the image attribute, the value is actually a cgi.FieldStorage dictionary, and thus I have to handle it in a special way, and here are my two problems:

  1. I didn't find a way to find out the specific custom Image class (i.e. given a Tool find out that its image column is actually an IconImage), so that I could write:
def create_instance(class_, data):
    instance = class_()
    for key, value in data.items():
        if "is the image attribute":
            custom_image_class = find_custom_image_class(class_, key)
            setattr(instance, key, custom_image_class.create_from(value))
        else:
            setattr(instance, key, value)

This is because the actual class is buried very deep in the SA Mutable machinery, and thus class_.image.property.expression gives you Json, not IconImage...

  1. As a workaround, I tried this:
def create_instance(class_, data):
    instance = class_()
    for key, value in data.items():
        if "is the image attribute":
            setattr(instance, key, (value['fp'], value['mimetype'], value['filename']))
        else:
            setattr(instance, key, value)

to delegate the logic to the Attachment.coerce() class method, but that fails too, due to the way it forwards the argument to Attachment.create_from(): IMHO it should handle the case when value is a tuple, and when that's the case call cls.create_from(*value)... and indeed the following monkey patch seems to satisfy my needs:

def coerce(cls, key, value) -> 'Attachment':
    """
    Converts plain dictionary to instance of this class.

    .. seealso:: :meth:`sqlalchemy.ext.mutable.MutableDict.coerce`

    """
    if value is not None and not isinstance(value, dict):
        try:
            cls._assert_type(value)
        except TypeError:
            if cls.__auto_coercion__:
                if isinstance(value, tuple):
                    return cls.create_from(*value)
                else:
                    return cls.create_from(value)
            raise

    return super(SAMAttachment, cls).coerce(key, value)


SAMAttachment.coerce = classmethod(coerce)

What do you think? Am I either missing something or abusing the API, or is there a better way to accomplish the above?

Thank you in advance for any hint!

Metadata

Metadata

Assignees

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions