Esempio n. 1
0
def fromdict(model,
             data,
             exclude=None,
             exclude_underscore=None,
             allow_pk=None,
             follow=None,
             include=None,
             only=None):
    """Update a model from a dict

    Works almost identically as :meth:`dictalchemy.utils.asdict`. However, it
    will not create missing instances or update collections.

    This method updates the following properties on a model:

    * Simple columns
    * Synonyms
    * Simple 1-m relationships

    :param data: dict of data
    :param exclude: list of properties that should be excluded
    :param exclude_underscore: If True underscore properties will be excluded,\
            if set to None model.dictalchemy_exclude_underscore will be used.
    :param allow_pk: If True any column that refers to the primary key will \
            be excluded. Defaults model.dictalchemy_fromdict_allow_pk or \
            dictable.constants.fromdict_allow_pk. If set to True a primary \
            key can still be excluded with the `exclude` parameter.
    :param follow: Dict of relations that should be followed, the key is the \
            arguments passed to the relation. Relations only works on simple \
            relations, not on lists.
    :param include: List of properties that should be included. This list \
            will override anything in the exclude list. It will not override \
            allow_pk.
    :param only: List of the only properties that should be returned. This \
            will not override `allow_pk` or `follow`.

    :raises: :class:`dictalchemy.errors.DictalchemyError` If a primary key is \
            in data and allow_pk is False

    :returns: The model

    """

    follow = arg_to_dict(follow)

    info = inspect(model)
    columns = [c.key for c in info.mapper.column_attrs]
    synonyms = [c.key for c in info.mapper.synonyms]
    relations = [c.key for c in info.mapper.relationships]
    primary_keys = [c.key for c in info.mapper.primary_key]

    if only:
        attrs = only
    else:
        exclude = exclude or []
        exclude += getattr(model, 'dictalchemy_exclude',
                           constants.default_exclude) or []
        if exclude_underscore is None:
            exclude_underscore = getattr(model,
                                         'dictalchemy_exclude_underscore',
                                         constants.default_exclude_underscore)

        if exclude_underscore:
            # Exclude all properties starting with underscore
            exclude += [k.key for k in info.mapper.attrs if k.key[0] == '_']

        if allow_pk is None:
            allow_pk = getattr(model, 'dictalchemy_fromdict_allow_pk',
                               constants.default_fromdict_allow_pk)

        include = (include or []) + (getattr(
            model, 'dictalchemy_fromdict_include',
            getattr(model, 'dictalchemy_include', None)) or [])
        attrs = [k for k in columns + synonyms if k not in exclude] + include

    # Update simple data
    for k, v in data.iteritems():
        if not allow_pk and k in primary_keys:
            msg = "Primary key({0}) cannot be updated by fromdict."
            "Set 'dictalchemy_fromdict_allow_pk' to True in your Model"
            " or pass 'allow_pk=True'.".format(k)
            raise errors.DictalchemyError(msg)
        if k in attrs:
            setattr(model, k, v)

    # Update simple relations
    for (k, args) in follow.iteritems():
        if k not in data:
            continue
        if k not in relations:
            raise errors.MissingRelationError(k)
        rel = getattr(model, k)
        if hasattr(rel, 'fromdict'):
            rel.fromdict(data[k], **args)

    return model
Esempio n. 2
0
def asdict(model,
           exclude=None,
           exclude_underscore=None,
           exclude_pk=None,
           follow=None,
           include=None,
           only=None,
           method='asdict',
           **kwargs):
    """Get a dict from a model

    Using the `method` parameter makes it possible to have multiple methods
    that formats the result.

    Additional keyword arguments will be passed to all relationships that are
    followed. This can be used to pass on things like request or context.

    :param follow: List or dict of relationships that should be followed.
            If the parameter is a dict the value should be a dict of \
            keyword arguments. Currently it follows InstrumentedList, \
            MappedCollection and regular 1:1, 1:m, m:m relationships. Follow \
            takes an extra argument, 'method', which is the method that \
            should be used on the relation. It also takes the extra argument \
            'parent' which determines where the relationships data should be \
            added in the response dict. If 'parent' is set the relationship \
            will be added with it's own key as a child to `parent`.
    :param exclude: List of properties that should be excluded, will be \
            merged with `model.dictalchemy_exclude`
    :param exclude_pk: If True any column that refers to the primary key will \
            be excluded.
    :param exclude_underscore: Overides `model.dictalchemy_exclude_underscore`\
            if set
    :param include: List of properties that should be included. Use this to \
            allow python properties to be called. This list will be merged \
            with `model.dictalchemy_asdict_include` or \
            `model.dictalchemy_include`.
    :param only: List of properties that should be included. This will \
            override everything else except `follow`.
    :param method: Name of the method that is currently called. This will be \
            the default method used in 'follow' unless another method is\
            set.

    :raises: :class:`dictalchemy.errors.MissingRelationError` \
            if `follow` contains a non-existent relationship.
    :raises: :class:`dictalchemy.errors.UnsupportedRelationError` If `follow` \
            contains an existing relationship that currently isn't supported.

    :returns: dict

    """

    follow = arg_to_dict(follow)

    info = inspect(model)

    columns = [c.key for c in info.mapper.column_attrs]
    synonyms = [c.key for c in info.mapper.synonyms]

    if only:
        attrs = only
    else:
        exclude = exclude or []
        exclude += getattr(model, 'dictalchemy_exclude',
                           constants.default_exclude) or []
        if exclude_underscore is None:
            exclude_underscore = getattr(model,
                                         'dictalchemy_exclude_underscore',
                                         constants.default_exclude_underscore)
        if exclude_underscore:
            # Exclude all properties starting with underscore
            exclude += [k.key for k in info.mapper.attrs if k.key[0] == '_']
        if exclude_pk is True:
            exclude += [c.key for c in info.mapper.primary_key]

        include = (include or []) + (getattr(
            model, 'dictalchemy_asdict_include',
            getattr(model, 'dictalchemy_include', None)) or [])
        attrs = [k for k in columns + synonyms + include if k not in exclude]

    data = dict([(k, getattr(model, k)) for k in attrs])

    for (rel_key, orig_args) in follow.iteritems():

        try:
            rel = getattr(model, rel_key)
        except AttributeError:
            raise errors.MissingRelationError(rel_key)

        args = copy.deepcopy(orig_args)
        method = args.pop('method', method)
        args['method'] = method
        args.update(copy.copy(kwargs))

        if hasattr(rel, method):
            rel_data = getattr(rel, method)(**args)
        elif isinstance(rel, (list, _AssociationList)):
            rel_data = []

            for child in rel:
                if hasattr(child, method):
                    rel_data.append(getattr(child, method)(**args))
                else:
                    try:
                        rel_data.append(dict(child))
                        # TypeError is for non-dictable children
                    except TypeError:
                        rel_data.append(copy.copy(child))

        elif isinstance(rel, dict):
            rel_data = {}

            for (child_key, child) in rel.iteritems():
                if hasattr(child, method):
                    rel_data[child_key] = getattr(child, method)(**args)
                else:
                    try:
                        rel_data[child_key] = dict(child)
                    except ValueError:
                        rel_data[child_key] = copy.copy(child)

        elif isinstance(rel, (AppenderMixin, Query)):
            rel_data = []

            for child in rel.all():
                if hasattr(child, method):
                    rel_data.append(getattr(child, method)(**args))
                else:
                    rel_data.append(dict(child))

        elif rel is None:
            rel_data = None
        else:
            raise errors.UnsupportedRelationError(rel_key)

        ins_key = args.pop('parent', None)

        if ins_key is None:
            data[rel_key] = rel_data
        else:
            if ins_key not in data:
                data[ins_key] = {}

            data[ins_key][rel_key] = rel_data

    return data
Esempio n. 3
0
def asdict(model,
           exclude=None,
           exclude_underscore=None,
           exclude_pk=None,
           follow=None,
           include=None,
           only=None):
    """Get a dict from a model

    :param follow: List or dict of relationships that should be followed. \
            If the parameter is a dict the value should be a dict of \
            keyword arguments. Currently it follows InstrumentedList,\
            MappedCollection and regular 1:1, 1:m, m:m relationships.
    :param exclude: List of properties that should be excluded, will be \
            merged with `model.dictalchemy_exclude`
    :param exclude_pk: If True any column that refers to the primary key will \
            be excluded.
    :param exclude_underscore: Overides `model.dictalchemy_exclude_underscore`\
            if set
    :param include: List of properties that should be included. Use this to \
            allow python properties to be called. This list will be merged \
            with `model.dictalchemy_asdict_include` or \
            `model.dictalchemy_include`.
    :param only: List of properties that should be included. This will \
            override everything else except `follow`.

    :raises: :class:`dictalchemy.errors.MissingRelationError` \
            if `follow` contains a non-existent relationship.
    :raises: :class:`dictalchemy.errors.UnsupportedRelationError` If `follow` \
            contains an existing relationship that currently isn't supported.

    :returns: dict

    """

    if follow is None:
        follow = []
    try:
        follow = dict(follow)
    except ValueError:
        follow = dict.fromkeys(list(follow), {})

    columns = get_column_keys(model)
    synonyms = get_synonym_keys(model)
    relations = get_relation_keys(model)

    if only:
        attrs = only
    else:
        exclude = exclude or []
        exclude += getattr(model, 'dictalchemy_exclude',
                           constants.default_exclude) or []
        if exclude_underscore is None:
            exclude_underscore = getattr(model,
                                         'dictalchemy_exclude_underscore',
                                         constants.default_exclude_underscore)
        if exclude_underscore:
            # Exclude all properties starting with underscore
            exclude += [
                k.key for k in model.__mapper__.iterate_properties
                if k.key[0] == '_'
            ]
        if exclude_pk is True:
            exclude += get_primary_key_properties(model)

        include = (include or []) + (getattr(
            model, 'dictalchemy_asdict_include',
            getattr(model, 'dictalchemy_include', None)) or [])
        attrs = [k for k in columns + synonyms + include if k not in exclude]

    data = dict([(k, getattr(model, k)) for k in attrs])

    for (k, args) in follow.iteritems():
        if k not in relations:
            raise errors.MissingRelationError(k)
        rel = getattr(model, k)
        if hasattr(rel, 'asdict'):
            data.update({k: rel.asdict(**args)})
        elif isinstance(rel, InstrumentedList):
            children = []
            for child in rel:
                if hasattr(child, 'asdict'):
                    children.append(child.asdict(**args))
                else:
                    children.append(dict(child))
            data.update({k: children})
        elif isinstance(rel, MappedCollection):
            children = {}
            for (child_key, child) in rel.iteritems():
                if hasattr(child, 'asdict'):
                    children[child_key] = child.asdict(**args)
                else:
                    children[child_key] = child.dict(child)
            data.update({k: children})
        elif isinstance(rel, (AppenderMixin, Query)):
            children = []
            for child in rel.all():
                if hasattr(child, 'asdict'):
                    children.append(child.asdict(**args))
                else:
                    children.append(dict(child))
            data.update({k: children})
        elif rel is None:
            data.update({k: None})
        else:
            raise errors.UnsupportedRelationError(k)

    return data