def _generate_auto_mapping(name, from_fields, to_fields): """ Generate the auto mapping between two fields. """ from_field = from_fields[name] to_field = to_fields[name] name = force_tuple(name) # Handle ListOf fields if isinstance(from_field, ListOf) and isinstance(to_field, ListOf): try: mapping = registration.get_mapping(from_field.of, to_field.of) return define(name, MapListOf(mapping), name, to_list=False, bind=True) except KeyError: # If both items are from and to fields refer to the same object automatically use a mapper that just # produces a clone. if from_field.of is to_field.of: return define(name, MapListOf(NoOpMapper), name, to_list=False, bind=True) # Handle DictAs fields elif isinstance(from_field, DictAs) and isinstance(to_field, DictAs): try: mapping = registration.get_mapping(from_field.of, to_field.of) return define(name, MapDictAs(mapping), name, bind=True) except KeyError: # If both items are from and to fields refer to the same object automatically use a mapper that just # produces a clone. if from_field.of is to_field.of: return define(name, MapDictAs(NoOpMapper), name, bind=True) return define(name, None, name)
def __init__(self, *args, **kwargs): super(ModelResourceApi, self).__init__(*args, **kwargs) assert self.model, "A model has not been provided." # Attempt to resolve mappings if self.to_model_mapping is None: self.to_model_mapping = registration.get_mapping(self.resource, self.model) if self.to_resource_mapping is None: self.to_resource_mapping = registration.get_mapping(self.model, self.resource)
def from_model(cls, model, context=None, **field_values): """ Convert this resource into a specified to resource. A mapping must be defined for conversion between this resource and to_resource or an exception will be raised. """ mapping = registration.get_mapping(cls.model, cls) return mapping(model, context).convert(**field_values)
def generate_dict_to_dict_mapping(mcs, name, from_field, to_field): try: mapping = registration.get_mapping(from_field.of, to_field.of) return define(name, MapDictAs(mapping), name, bind=True) except KeyError: # If both items are from and to fields refer to the same object automatically use a mapper that just # produces a clone. if from_field.of is to_field.of: return define(name, MapDictAs(NoOpMapper), name, bind=True)
def convert_to(self, to_resource, **field_values): """ Convert this resource into a specified to resource. A mapping must be defined for conversion between this resource and to_resource or an exception will be raised. """ self.full_clean() mapping = registration.get_mapping(self.__class__, to_resource) return mapping(self).convert(**field_values)
def update_existing(self, dest_obj, context=None, ignore_fields=None): """ Update the fields on an existing destination object. A mapping must be defined for conversion between this resource and ``dest_obj`` type or an exception will be raised. """ self.full_clean(ignore_fields) mapping = registration.get_mapping(self.__class__, dest_obj.__class__) return mapping(self, context).update(dest_obj, ignore_fields)
def convert_to(self, to_resource, context=None, ignore_fields=None, **field_values): """ Convert this resource into a specified resource. A mapping must be defined for conversion between this resource and to_resource or an exception will be raised. """ mapping = registration.get_mapping(self.__class__, to_resource) ignore_fields = ignore_fields or [] ignore_fields.extend(mapping.exclude_fields) self.full_clean(ignore_fields) return mapping(self, context).convert(**field_values)
def generate_list_to_list_mapping(mcs, name, from_field, to_field): """ Generate a mapping of list to list objects. """ try: mapping = registration.get_mapping(from_field.of, to_field.of) return FieldMapping(name, MapListOf(mapping), name, to_list=False, bind=True, skip_if_none=False) except KeyError: # If both items are from and to fields refer to the same object automatically use a mapper that just # produces a clone. if from_field.of is to_field.of: return FieldMapping(name, MapListOf(NoOpMapper), name, to_list=False, bind=True, skip_if_none=False)
def update(self, instance, context=None, commit=True, lazy=True): """ Update an existing model from this model. :param instance: Model instance to be updated. :param context: Context dict passed to each mapping function. :param commit: If True, then changes to the updated model instance will be saved to the database. :param lazy: If True, then instance and source resource are compared, if no changes are found no database, operation is performed. :return: Updated model instance. """ assert isinstance(instance, self.model) mapping = registration.get_mapping(self.__class__, self.model) mapper = mapping(self, context) if not lazy or len(mapper.diff(instance)) > 0: mapper.update(instance) if commit: instance.save() return instance
def __new__(cls, name, bases, attrs): super_new = super(MappingBase, cls).__new__ # attrs will never be empty for classes declared in the standard way # (ie. with the `class` keyword). This is quite robust. if name == 'NewBase' and attrs == {}: return super_new(cls, name, bases, attrs) parents = [b for b in bases if isinstance(b, MappingBase) and not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))] if not parents: # If this isn't a subclass of Mapping, don't do anything special. return super_new(cls, name, bases, attrs) from_resource = attrs.get('from_resource') if not (isinstance(from_resource, type) and issubclass(from_resource, Resource)): raise MappingSetupError('`from_resource` is not a Resource type') to_resource = attrs.get('to_resource') if not (isinstance(to_resource, type) and issubclass(to_resource, Resource)): raise MappingSetupError('`to_resource` is not a Resource type') # Check if we have already created this mapping try: return registration.get_mapping(from_resource, to_resource) except KeyError: pass # Not registered # Generate mapping rules. from_fields = from_resource._meta.field_map to_fields = to_resource._meta.field_map def attr_mapping_to_mapping_rule(m, def_type, ref): # Parse, validate and normalise defined mapping rules so rules can be executed without having to # perform checks during a mapping operation. to_list = False try: map_from, action, map_to, to_list = m except ValueError: try: map_from, action, map_to = m except ValueError: raise MappingSetupError('Bad mapping definition `%s` in %s `%s`.' % (m, def_type, ref)) map_from = force_tuple(map_from) for f in map_from: if not f in from_fields: raise MappingSetupError('Field `%s` of %s `%s` not found on from resource. ' % (f, def_type, ref)) if isinstance(action, six.string_types): if action not in attrs: raise MappingSetupError('Action named %s defined in %s `%s` was not defined on mapping object.' % ( action, def_type, ref)) if not callable(attrs[action]): raise MappingSetupError('Action named %s defined in %s `%s` is not callable.' % ( action, def_type, ref)) elif action is not None and not callable(action): raise MappingSetupError('Action on %s `%s` is not callable.' % (def_type, ref)) map_to = force_tuple(map_to) if to_list and len(map_to) != 1: raise MappingSetupError('The %s `%s` specifies a to_list mapping, these can only be applied to a ' 'single target field.' % (def_type, m)) for f in map_to: if not f in to_fields: raise MappingSetupError('Field `%s` of %s `%s` not found on to resource. ' % (f, def_type, ref)) return map_from, action, map_to, to_list def remove_from_unmapped_fields(rule): # Remove any fields that are handled by a mapping rule from unmapped_fields list. map_from, _, map_to, _ = rule if len(map_from) == 1 and map_from[0] in unmapped_fields: unmapped_fields.remove(map_from[0]) if len(map_to) == 1 and map_to[0] in unmapped_fields: unmapped_fields.remove(map_to[0]) exclude_fields = attrs.get('exclude_fields') or tuple() unmapped_fields = [attname for attname in from_fields if attname not in exclude_fields] mapping_rules = [] # Add basic mappings for idx, mapping in enumerate(attrs.pop('mappings', [])): mapping_rule = attr_mapping_to_mapping_rule(mapping, 'basic mapping', idx) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Add custom mappings for attr in attrs.values(): # Methods with a _mapping attribute have been decorated by either `map_field` or `map_list_field` # decorators. if hasattr(attr, '_mapping'): mapping_rule = attr_mapping_to_mapping_rule(getattr(attr, '_mapping'), 'custom mapping', attr) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Remove mapping delattr(attr, '_mapping') # Add auto mapped fields that are yet to be mapped. for field in unmapped_fields: if field in to_fields: mapping_rules.append(((field,), None, (field,), False)) # Update mappings attrs['_mapping_rules'] = mapping_rules registration.register_mappings(super_new(cls, name, bases, attrs)) return registration.get_mapping(from_resource, to_resource)
def __new__(mcs, name, bases, attrs): super_new = super(MappingMeta, mcs).__new__ # attrs will never be empty for classes declared in the standard way # (ie. with the `class` keyword). This is quite robust. if name == 'NewBase' and attrs == {}: return super_new(mcs, name, bases, attrs) parents = [ b for b in bases if isinstance(b, MappingMeta) and not (b.__name__ == 'NewBase' and b.__mro__ == (b, MappingBase, object)) ] if not parents: # If this isn't a subclass of Mapping, don't do anything special. return super_new(mcs, name, bases, attrs) # Backward compatibility from_resource -> from_obj from_obj = attrs.setdefault('from_obj', attrs.get('from_resource')) if from_obj is None: raise MappingSetupError('`from_obj` is not defined.') to_obj = attrs.setdefault('to_obj', attrs.get('to_resource')) if to_obj is None: raise MappingSetupError('`to_obj` is not defined.') register_mapping = attrs.pop('register_mapping', True) # Check if we have already created this mapping if register_mapping: try: return registration.get_mapping(from_obj, to_obj) except KeyError: pass # Not registered # Get field resolver objects try: from_fields = registration.get_field_resolver( from_obj).from_field_dict except KeyError: raise MappingSetupError( '`from_obj` %r does not have an attribute resolver defined.' % from_obj) try: to_fields = registration.get_field_resolver(to_obj).to_field_dict except KeyError: raise MappingSetupError( '`to_obj` %r does not have an attribute resolver defined.' % to_obj) def attr_mapping_to_mapping_rule(m, def_type, ref): """ Parse, validate and normalise defined mapping rules so rules can be executed without having to perform checks during a mapping operation.""" to_list = False bind = False skip_if_none = False is_assignment = False try: map_from, action, map_to, to_list, bind, skip_if_none = m except ValueError: try: map_from, action, map_to = m except ValueError: raise MappingSetupError( 'Bad mapping definition `%s` in %s `%s`.' % (m, def_type, ref)) if map_from is None: is_assignment = True if not is_assignment: map_from = force_tuple(map_from) for f in map_from: if f not in from_fields: raise MappingSetupError( 'Field `%s` of %s `%s` not found on from object. ' % (f, def_type, ref)) if isinstance(action, six.string_types): if action not in attrs: raise MappingSetupError( 'Action named %s defined in %s `%s` was not defined on mapping object.' % (action, def_type, ref)) if not callable(attrs[action]): raise MappingSetupError( 'Action named %s defined in %s `%s` is not callable.' % (action, def_type, ref)) elif action is not None and not callable(action): raise MappingSetupError('Action on %s `%s` is not callable.' % (def_type, ref)) elif action is None and is_assignment: raise MappingSetupError( 'No action supplied for `%s` in `%s`.' % (def_type, ref)) map_to = force_tuple(map_to) if to_list and len(map_to) != 1: raise MappingSetupError( 'The %s `%s` specifies a to_list mapping, these can only be applied to a ' 'single target field.' % (def_type, m)) for f in map_to: if f not in to_fields: raise MappingSetupError( 'Field `%s` of %s `%s` not found on to object. ' % (f, def_type, ref)) return FieldMapping(map_from, action, map_to, to_list, bind, skip_if_none) # Determine what fields need to have mappings generated exclude_fields = attrs.get('exclude_fields') or tuple() unmapped_fields = [ attname for attname in from_fields if attname not in exclude_fields ] def remove_from_unmapped_fields(rule): # Remove any fields that are handled by a mapping rule from unmapped_fields list. map_to = rule[2] if len(map_to) == 1 and map_to[0] in unmapped_fields: unmapped_fields.remove(map_to[0]) # Generate mapping rules. mapping_rules = [] # Check that from_obj is a sub_class (or same class) as any `parent.from_obj`. This is important for mapping # sub class lists and resolving mappings. base_parents = [p for p in parents if hasattr(p, '_subs')] for p in base_parents: if not issubclass(from_obj, p.from_obj): raise MappingSetupError( '`from_obj` must be a subclass of `parent.from_obj`') if not issubclass(to_obj, p.to_obj): raise MappingSetupError( '`to_obj` must be a subclass of `parent.to_obj`') # Copy mapping rules for mapping_rule in p._mapping_rules: mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Add basic mappings for idx, mapping in enumerate(attrs.pop('mappings', [])): mapping_rule = attr_mapping_to_mapping_rule( mapping, 'basic mapping', idx) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Add custom mappings for attr in attrs.values(): # Methods with a _mapping attribute have been decorated by either `map_field` or `map_list_field` # decorators. if hasattr(attr, '_mapping'): mapping_rule = attr_mapping_to_mapping_rule( getattr(attr, '_mapping'), 'custom mapping', attr) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Remove mapping delattr(attr, '_mapping') # Add auto mapped fields that are yet to be mapped. for field in unmapped_fields: if field in to_fields: mapping_rules.append( mcs.generate_auto_mapping(field, from_fields, to_fields)) # Update attributes attrs['_mapping_rules'] = mapping_rules attrs['_subs'] = {} # Create mapper instance mapper = super_new(mcs, name, bases, attrs) if register_mapping: registration.register_mapping(mapper) mapper = registration.get_mapping(from_obj, to_obj) # Register mapping with parents mapping objects as a sub class. for parent in base_parents: parent._subs[from_obj] = mapper return mapper
def __new__(mcs, name, bases, attrs): super_new = super(MappingMeta, mcs).__new__ # attrs will never be empty for classes declared in the standard way # (ie. with the `class` keyword). This is quite robust. if name == 'NewBase' and attrs == {}: return super_new(mcs, name, bases, attrs) parents = [ b for b in bases if isinstance(b, MappingMeta) and not (b.__name__ == 'NewBase' and b.__mro__ == (b, MappingBase, object)) ] if not parents: # If this isn't a subclass of Mapping, don't do anything special. return super_new(mcs, name, bases, attrs) # Backward compatibility from_resource -> from_obj from_obj = attrs.setdefault('from_obj', attrs.get('from_resource')) if from_obj is None: raise MappingSetupError('`from_obj` is not defined.') to_obj = attrs.setdefault('to_obj', attrs.get('to_resource')) if to_obj is None: raise MappingSetupError('`to_obj` is not defined.') register_mapping = attrs.pop('register_mapping', True) # Check if we have already created this mapping if register_mapping: try: return registration.get_mapping(from_obj, to_obj) except KeyError: pass # Not registered # Get field resolver objects try: from_fields = registration.get_field_resolver(from_obj).from_field_dict except KeyError: raise MappingSetupError('`from_obj` %r does not have an attribute resolver defined.' % from_obj) try: to_fields = registration.get_field_resolver(to_obj).to_field_dict except KeyError: raise MappingSetupError('`to_obj` %r does not have an attribute resolver defined.' % to_obj) def attr_mapping_to_mapping_rule(m, def_type, ref): """ Parse, validate and normalise defined mapping rules so rules can be executed without having to perform checks during a mapping operation.""" to_list = False bind = False skip_if_none = False is_assignment = False try: map_from, action, map_to, to_list, bind, skip_if_none = m except ValueError: try: map_from, action, map_to = m except ValueError: raise MappingSetupError('Bad mapping definition `%s` in %s `%s`.' % (m, def_type, ref)) if map_from is None: is_assignment = True if not is_assignment: map_from = force_tuple(map_from) for f in map_from: if f not in from_fields: raise MappingSetupError('Field `%s` of %s `%s` not found on from object. ' % (f, def_type, ref)) if isinstance(action, six.string_types): if action not in attrs: raise MappingSetupError('Action named %s defined in %s `%s` was not defined on mapping object.' % ( action, def_type, ref)) if not callable(attrs[action]): raise MappingSetupError('Action named %s defined in %s `%s` is not callable.' % ( action, def_type, ref)) elif action is not None and not callable(action): raise MappingSetupError('Action on %s `%s` is not callable.' % (def_type, ref)) elif action is None and is_assignment: raise MappingSetupError('No action supplied for `%s` in `%s`.' % (def_type, ref)) map_to = force_tuple(map_to) if to_list and len(map_to) != 1: raise MappingSetupError('The %s `%s` specifies a to_list mapping, these can only be applied to a ' 'single target field.' % (def_type, m)) for f in map_to: if f not in to_fields: raise MappingSetupError('Field `%s` of %s `%s` not found on to object. ' % (f, def_type, ref)) return FieldMapping(map_from, action, map_to, to_list, bind, skip_if_none) # Determine what fields need to have mappings generated exclude_fields = attrs.get('exclude_fields') or () unmapped_fields = [attname for attname in from_fields if attname not in exclude_fields] def remove_from_unmapped_fields(rule): # Remove any fields that are handled by a mapping rule from unmapped_fields list. map_to = rule[2] if len(map_to) == 1 and map_to[0] in unmapped_fields: unmapped_fields.remove(map_to[0]) # Generate mapping rules. mapping_rules = [] # Check that from_obj is a sub_class (or same class) as any `parent.from_obj`. This is important for mapping # sub class lists and resolving mappings. base_parents = [p for p in parents if hasattr(p, '_subs')] for p in base_parents: if not issubclass(from_obj, p.from_obj): raise MappingSetupError('`from_obj` must be a subclass of `parent.from_obj`') if not issubclass(to_obj, p.to_obj): raise MappingSetupError('`to_obj` must be a subclass of `parent.to_obj`') # Copy mapping rules for mapping_rule in p._mapping_rules: mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Add basic mappings for idx, mapping in enumerate(attrs.pop('mappings', [])): mapping_rule = attr_mapping_to_mapping_rule(mapping, 'basic mapping', idx) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Add custom mappings for attr in attrs.values(): # Methods with a _mapping attribute have been decorated by either `map_field` or `map_list_field` # decorators. if hasattr(attr, '_mapping'): mapping_rule = attr_mapping_to_mapping_rule(getattr(attr, '_mapping'), 'custom mapping', attr) mapping_rules.append(mapping_rule) remove_from_unmapped_fields(mapping_rule) # Remove mapping delattr(attr, '_mapping') # Add auto mapped fields that are yet to be mapped. for field in unmapped_fields: if field in to_fields: mapping_rules.append(mcs.generate_auto_mapping(field, from_fields, to_fields)) # Update attributes attrs['_mapping_rules'] = mapping_rules attrs['_subs'] = {} # Create mapper instance mapper = super_new(mcs, name, bases, attrs) if register_mapping: registration.register_mapping(mapper) mapper = registration.get_mapping(from_obj, to_obj) # Register mapping with parents mapping objects as a sub class. for parent in base_parents: parent._subs[from_obj] = mapper return mapper