def _unpack(self, name, attrib, load_collections): "Helper method to unpack SQLalchemy objects" if attrib is None: return if name == "metatags": return self._unpack_metatags(attrib) #log.d("name:", name, "attrib:", attrib) # beware lots of recursion if db.is_instanced(attrib): msg_obj = None exclude = (db.NameMixin.__name__, ) for cls_name, cls_obj in self._db_clsmembers: if cls_name not in exclude: if isinstance(attrib, cls_obj): if cls_name in self._clsmembers: msg_obj = self._clsmembers[cls_name](attrib) break if not msg_obj: if isinstance(attrib, db.NameMixin): msg_obj = NameMixin(attrib, name) else: raise NotImplementedError( "Message encapsulation for this database object does not exist ({})" .format(type(attrib))) return msg_obj.data() if msg_obj else None elif db.is_list(attrib) or isinstance(attrib, list): return [self._unpack(name, x, load_collections) for x in attrib] elif db.is_query(attrib): if load_collections: return [ self._unpack(name, x, load_collections) for x in attrib.all() ] else: return [] elif isinstance(attrib, enum.Enum): return attrib.name elif isinstance(attrib, datetime): return attrib.timestamp() elif isinstance(attrib, arrow.Arrow): return attrib.timestamp elif isinstance(attrib, (bool, int, float, str, dict)): return attrib else: raise NotImplementedError( "Unpacking method for this attribute does not exist ({})". format(type(attrib)))
def _unpack_metatags(self, attrib): m_tags = {x: False for x in db.MetaTag.all_names()} names = [] if db.is_query(attrib): names = tuple(x.name for x in attrib.all()) elif db.is_list(attrib) or isinstance(attrib, list): names = tuple(x.name for x in attrib) for n in names: m_tags[n] = True return m_tags
def _unpack(self, name, attrib, load_values, load_collections, propagate_bypass): "Helper method to unpack SQLalchemy objects" if attrib is None: return if name == "metatags": return self._unpack_metatags(attrib) #log.d("name:", name, "attrib:", attrib) # beware lots of recursion if db.is_instanced(attrib): msg_obj = self._get_message_object(self, name, attrib) if msg_obj is False: # recursive checked return None msg_obj._indirect = True msg_obj._properties['force_value_load'] = utils.dict_merge( msg_obj._properties['force_value_load'], self._properties['force_value_load'].get(name, {})) msg_obj._properties['exclusions'] = self._properties['exclusions'].get(name, {}) if self._detached: msg_obj._detached = self._detached msg_obj._msg_path = self._msg_path.copy() # note: don't pass load_collections here. use the force_value_load param return msg_obj.data(load_values=load_values, bypass_exclusions=propagate_bypass, propagate_bypass=propagate_bypass) if msg_obj else None elif db.is_list(attrib) or isinstance(attrib, list): return [self._unpack(name, x, load_values, load_collections, propagate_bypass) for x in attrib] elif db.is_query(attrib): can_load = False if name in self._properties['force_value_load']: can_load = True if can_load or (load_collections and not self._detached): return [self._unpack(name, x, load_values, load_collections, propagate_bypass) for x in attrib.all()] else: raise DatabaseMessage.NoUnpack elif isinstance(attrib, enum.Enum): return attrib.name elif isinstance(attrib, datetime): return attrib.timestamp() elif isinstance(attrib, arrow.Arrow): return attrib.timestamp elif isinstance(attrib, (bool, int, float, str, dict)): return attrib else: raise NotImplementedError( "Unpacking method for this attribute does not exist ({})".format( type(attrib)))
def from_json(cls, msg, ignore_empty=True, skip_updating_existing=True, skip_descriptors=False, _type=None): db_obj = None with db.no_autoflush(constants.db_session()) as sess: if not cls.db_type and _type is None: raise ValueError("A database type has not been set") db_type = _type or cls.db_type item_attrs = db.table_attribs(db_type, id=None, descriptors=True) if msg and not all((k in item_attrs.keys() for k in msg.keys())): m_keys = set(msg.keys()).difference(set(item_attrs.keys())) raise exceptions.InvalidMessage( utils.this_function(), f"Message object mismatch. Message contains keys that are not present in the corresponding database item: {m_keys}" ) if msg: obj_id = msg.get('id', False) if obj_id: db_obj = sess.query(db_type).get(obj_id) else: db_obj = db_type() if not (obj_id and db_obj and skip_updating_existing): for attr, value in msg.items(): if ignore_empty: if value is None: continue elif isinstance(value, (list, dict)) and not value: continue cls_attr = item_attrs[attr] if skip_descriptors and db.is_descriptor(cls_attr): continue obj_attr = getattr(db_obj, attr) try: col_model = db.column_model(cls_attr) except TypeError: # most likely a hybrid_property descriptor col_model = None if not issubclass(col_model, db.Base) if inspect.isclass( col_model) else not isinstance( col_model, db.Base): if isinstance( col_model, (db.Boolean, db.Integer, db.Password, db.String, db.Text, db.LowerCaseString, db.CapitalizedString)): setattr(db_obj, attr, value) elif isinstance(col_model, db.ArrowType): setattr(db_obj, attr, arrow.Arrow.fromtimestamp(value)) elif db.is_descriptor(cls_attr): if db.descriptor_has_setter(cls_attr): raise NotImplementedError else: raise NotImplementedError( f"Value deserializing for this database type does not exist ({col_model})" ) continue msg_obj = cls._get_message_object( cls, attr, col_model, check_recursive=False, class_type=True) if issubclass(col_model, db.MetaTag): setattr(db_obj, attr, msg_obj._pack_metatags(value)) elif db.is_list(cls_attr) or db.is_query(cls_attr): for v in value: obj_attr.append( msg_obj.from_json( v, _type=col_model, ignore_empty=ignore_empty, skip_updating_existing= skip_updating_existing, skip_descriptors=skip_descriptors)) elif db.is_descriptor(cls_attr): if db.descriptor_has_setter(cls_attr): raise NotImplementedError else: setattr( db_obj, attr, msg_obj.from_json( value, _type=col_model, ignore_empty=ignore_empty, skip_updating_existing= skip_updating_existing, skip_descriptors=skip_descriptors)) return db_obj
def from_json(cls, msg, ignore_empty=True, skip_updating_existing=True, skip_descriptors=False, _type=None, ignore_private=True, _from_attr=''): db_obj = None if not msg: return db_obj if not isinstance(msg, dict): raise exceptions.InvalidMessage( utils.this_function(), f"Expected message object on '{_from_attr}' not '{type(msg).__name__}'") with db.no_autoflush(constants.db_session()) as sess: if not cls.db_type and _type is None: raise ValueError("A database type has not been set") db_type = _type or cls.db_type item_attrs = db.table_attribs(db_type, id=None, descriptors=True) if msg and not all((k in item_attrs.keys() for k in msg.keys())): m_keys = set(msg.keys()).difference(set(item_attrs.keys())) raise exceptions.InvalidMessage( utils.this_function(), f"Message object mismatch. '{_from_attr}' contains keys that are not present in the corresponding database item: {m_keys}") if msg: new_obj = True obj_id = msg.get('id', False) if obj_id: db_obj = sess.query(db_type).get(obj_id) if not db_obj: raise exceptions.CoreError(utils.this_function(), f"Existing item from message object with id '{obj_id}' was not found") new_obj = False else: db_obj = db_type() if new_obj and isinstance(db_obj, db.UniqueMixin): if isinstance(db_obj, db.NameMixin) and msg.get('name') is not None: db_obj = db_type.as_unique(name=msg.get('name')) new_obj = bool(db_obj.id) elif isinstance(db_obj, db.NamespaceTags): ns_name = None tag_name = None try: ns_name = utils.get_dict_value('namespace.name', msg) tag_name = utils.get_dict_value('tag.name', msg) except KeyError: pass if ns_name is not None and tag_name is not None: db_obj = db_type.as_unique(ns=ns_name, tag=tag_name) new_obj = bool(db_obj.id) elif isinstance(db_obj, (db.Artist, db.Parody)): a_names = set() if msg.get('preferred_name') and msg['preferred_name'].get('name') is not None: a_names.add(msg['preferred_name']['name']) if msg.get('names'): for n in msg['names']: if n.get('name') is not None: a_names.add(n['name']) if a_names: db_obj = db_type.as_unique(names=a_names) new_obj = bool(db_obj.id) if db_obj and not (obj_id and db_obj and skip_updating_existing): for attr, value in msg.items(): if attr == 'id': continue if ignore_private and attr.startswith('_'): continue if ignore_empty: if value is None: continue elif isinstance(value, (list, dict)) and not value: continue cls_attr = item_attrs[attr] if skip_descriptors and db.is_descriptor(cls_attr): continue obj_attr = getattr(db_obj, attr) # noqa: F841 try: col_model = db.column_model(cls_attr) except TypeError: # most likely a hybrid_property descriptor col_model = None if not issubclass(col_model, db.Base) if inspect.isclass( col_model) else not isinstance(col_model, db.Base): if isinstance(col_model, (db.Boolean, db.Integer, db.Password, db.String, db.Text, db.LowerCaseString, db.CapitalizedString)): setattr(db_obj, attr, value) elif isinstance(col_model, db.ArrowType): setattr(db_obj, attr, arrow.Arrow.fromtimestamp(value)) elif db.is_descriptor(cls_attr): if db.descriptor_has_setter(cls_attr): raise NotImplementedError else: raise NotImplementedError( f"Value deserializing for this database type does not exist ({col_model})") continue msg_obj = cls._get_message_object(cls, attr, col_model, check_recursive=False, class_type=True) if col_model == db.Taggable and isinstance(value, dict): if db_obj.taggable and db_obj.taggable.id: value['id'] = db_obj.taggable.id if issubclass(col_model, db.MetaTag): setattr(db_obj, attr, msg_obj._pack_metatags(value)) elif db.is_list(cls_attr) or db.is_query(cls_attr): n_l = [] for v in value: o = msg_obj.from_json( v, _type=col_model, ignore_empty=ignore_empty, skip_updating_existing=skip_updating_existing, skip_descriptors=skip_descriptors, _from_attr=_from_attr + '.' + attr if _from_attr else attr, ) if o is not None: n_l.append(o) setattr(db_obj, attr, n_l) elif db.is_descriptor(cls_attr): if db.descriptor_has_setter(cls_attr): raise NotImplementedError else: setattr( db_obj, attr, msg_obj.from_json( value, _type=col_model, _from_attr=_from_attr + '.' + attr if _from_attr else attr, ignore_empty=ignore_empty, skip_updating_existing=skip_updating_existing, skip_descriptors=skip_descriptors)) return db_obj