Example #1
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
Example #2
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