Description
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:
- I didn't find a way to find out the specific custom
Image
class (i.e. given aTool
find out that itsimage
column is actually anIconImage
), 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
...
- 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!