예제 #1
0
    async def get_collection(self, args, **kwargs):
        """
        Fetch a collection of resources.

        >>> from jsonapi.tests.model import UserModel
        >>> await UserModel().get_collection({})
        {'data':
            [
                {'id': '1',
                 'type': 'user',
                 'attributes': {
                     'email': '*****@*****.**',
                     'first': 'Robert',
                     'last': 'Camacho',
                     'createdOn': '2019-05-18T11:49:43Z',
                     'status': 'active',
                     'name': 'Robert Camacho'}
                 },
                ...
            ]
        }

        :param dict args: a dictionary representing the request query string
        :param str search: an optional search term
        :return: JSON API response document
        """
        search_term = kwargs.pop('search', None) or args.pop('search', None)
        args = self.parse_arguments(args)
        self.init_schema(args)
        filter_by, order_by = self.get_filter_by(args), self.get_order_by(args)
        where = None
        if 'where' in kwargs:
            where = kwargs['where'](self.rec)
        query = select_many(self,
                            filter_by=filter_by,
                            order_by=order_by,
                            offset=args.page.offset,
                            limit=args.page.limit,
                            search_term=search_term,
                            where=where)
        log_query(query)
        recs = {rec['id']: dict(rec) for rec in await pg.fetch(query)}
        recs = list(recs.values())
        await self.set_meta(args.page.limit,
                            filter_by=filter_by,
                            search_term=search_term,
                            where=where)
        self.check_size(args, recs)
        await self.fetch_included(recs, args)
        return self.response(recs)
예제 #2
0
    async def get_merged(self, args, object_ids, relationship_name, **kwargs):
        self.init_schema()
        for object_id in object_ids:
            if not await pg.fetchval(exists(self, object_id)):
                raise NotFound(object_id, self)

        recs = list()
        for object_id in object_ids:
            rec = await pg.fetchrow(select_one(self, object_id))
            if rec is None:
                raise Forbidden(object_id, self)
            recs.append(rec)

        rel = self.relationship(relationship_name)
        if rel.cardinality != Cardinality.MANY_TO_MANY:
            raise APIError(
                'get_merge works only with many-to-many relationships', self)

        args = self.parse_arguments(args)
        rel.load(self)
        rel.model.init_schema(args)
        filter_by, order_by = rel.model.get_filter_by(
            args), rel.model.get_order_by(args)
        exclude = set(kwargs.pop('exclude', ()))
        query = select_merged(self,
                              rel,
                              object_ids,
                              filter_by=filter_by,
                              order_by=order_by,
                              offset=args.page.offset,
                              limit=args.page.limit,
                              exclude=exclude,
                              options=args.merge)
        log_query(query)
        data = {rec['id']: dict(rec) for rec in await pg.fetch(query)}
        data = list(data.values())
        await rel.model.set_meta(args.page.limit,
                                 object_ids,
                                 rel,
                                 exclude=exclude,
                                 options=args.merge,
                                 filter_by=filter_by,
                                 merge=True)
        rel.model.check_size(args, data)
        await rel.model.fetch_included(data, args)
        return rel.model.response(data)
예제 #3
0
    async def get_object(self, args, object_id):
        """
        Fetch a resource object.

        >>> from jsonapi.tests.model import UserModel
        >>> await UserModel().get_object({}, 1)
        {
            'data': {
                'id': '1',
                'type': 'user',
                'attributes': {
                    'email': '*****@*****.**',
                    'first': 'Robert',
                    'last': 'Camacho'
                }
            }
        }
        >>> await UserModel().get_object({}, email='*****@*****.**')
        >>> await UserModel().get_object({}, first='Robert', last: 'Camacho'})

        :param dict args: a dictionary representing the request query string
        :param int|str|dict object_id: the resource object id
        :return: JSON API response document
        """
        args = self.parse_arguments(args)
        self.init_schema(args)

        if not await pg.fetchval(exists(self, object_id)):
            raise NotFound(object_id, self)

        query = select_one(self, object_id)
        log_query(query)
        result = await pg.fetchrow(query)
        if result is None:
            raise Forbidden(object_id, self)
        rec = dict(result)
        await self.fetch_included([rec], args)
        return self.response(rec)
예제 #4
0
    async def fetch_included(self, data, args):

        if not isinstance(data, list):
            data = list() if data is None else [data]

        for rec in data:
            rec['type'] = self.type_

        for rel in self.relationships.values():
            result = list()
            for query in select_related(rel,
                                        list(set(rec['id'] for rec in data))):
                log_query(query)
                result.extend(await pg.fetch(query))

            recs_by_parent_id = defaultdict(list)
            for rec in result:
                rec = dict(rec)
                parent_id = rec.pop('parent_id')
                recs_by_parent_id[parent_id].append(rec)

            for parent in data:
                parent_id = parent['id']
                if rel.cardinality in (Cardinality.ONE_TO_ONE,
                                       Cardinality.MANY_TO_ONE):
                    parent[rel.name] = recs_by_parent_id[parent_id][
                        0] if parent_id in recs_by_parent_id else None
                else:
                    parent[rel.name] = recs_by_parent_id[
                        parent_id] if parent_id in recs_by_parent_id else list(
                        )

            await rel.model.fetch_included(
                reduce(lambda a, b: a + b
                       if isinstance(b, list) else a + [b], [
                           rec[rel.name]
                           for rec in data if rec[rel.name] is not None
                       ], list()), args)
예제 #5
0
async def get_collection(args, *models, **kwargs):
    """
    Fetch a heterogeneous collection of objects.

    Returns a heterogeneous list of objects.

    >>> from jsonapi.model import search
    >>> search({'include[user]': 'bio',
    >>>         'include[article]': 'keywords,author.bio,publisher.bio',
    >>>         'fields[user]': 'name,email',
    >>>         'fields[user-bio]': 'birthday,age',
    >>>         'fields[article]': 'title'},
    >>>         'John', UserModel, ArticleModel)

    :param dict args: a dictionary representing the request query string
    :param mixed models: variable length list of model classes or instances
    :param str search: a PostgreSQL full text search query string (e.x. ``'foo:* & !bar'``)
    :return: JSON API response document
    """
    ra = parse_arguments(args)
    search_term = kwargs.pop('search', None) or args.pop('search', None)
    models = ModelSet(*models, searchable=search_term is not None)
    query = select_mixed(models, order_by=ra.sort, limit=ra.page.limit, offset=ra.page.offset) \
        if search_term is None else search_query(models, search_term, limit=ra.page.limit, offset=ra.page.offset)
    log_query(query)
    mixed = list()
    async with pg.query(query) as cursor:
        async for row in cursor:
            mixed.append(dict(type=row['resource_type'], id=row['id']))

    data = defaultdict(dict)
    included = defaultdict(dict)
    meta = {'total': 0, 'subTotal': dict()}
    for model in models:
        object_id = list(obj['id'] for obj in mixed
                         if model.type_ == obj['type'])
        if len(object_id) > 0:
            model_args = model.parse_arguments(_extract_model_args(
                model, args))
            model.init_schema(model_args)
            query = select_many(model,
                                filter_by=FilterBy(
                                    model.primary_key.in_([
                                        cast(x, model.primary_key.type)
                                        for x in object_id
                                    ])))
            log_query(query)
            recs = [{
                'type': model.type_,
                **rec
            } for rec in await pg.fetch(query)]
            await model.fetch_included(recs, model_args)
            for rec in model.schema.dump(recs, many=True):
                data[rec['type']][rec['id']] = rec
            if len(model.included) > 0:
                for resource_type in model.included.keys():
                    included[resource_type].update(
                        model.included[resource_type])
        model.reset()

    for (resource_type, query) in (select_mixed(models, count=True) \
            if search_term is None else search_query(models, search_term, count=True)):
        meta['subTotal'][resource_type] = await pg.fetchval(query)
        meta['total'] += meta['subTotal'][resource_type]
    return dict(data=[data[rec['type']][str(rec['id'])] for rec in mixed],
                included=reduce(lambda a, b: a + [r for r in b.values()],
                                included.values(), list()),
                meta=meta)
예제 #6
0
    async def get_related(self, args, object_id, relationship_name, **kwargs):
        """
        Fetch a collection of related resources.

        >>> from jsonapi.tests.model import ArticleModel
        >>> await ArticleModel().get_related({
        >>>     'include': 'articles.comments,articles.keywords',
        >>>     'fields[article]': 'title,body',
        >>>     'fields[comments]': 'body'
        >>> }, 1, 'author')

        :param dict args: a dictionary representing the request query string
        :param int|str|dict object_id: the resource object id
        :param str relationship_name: relationship name
        :param str search: an optional search term
        :return: JSON API response document
        """

        self.init_schema()
        if not await pg.fetchval(exists(self, object_id)):
            raise NotFound(object_id, self)

        rec = await pg.fetchrow(select_one(self, object_id))
        if rec is None:
            raise Forbidden(object_id, self)

        search_term = kwargs.pop('search', None) or args.pop('search', None)

        rel = self.relationship(relationship_name)
        args = self.parse_arguments(args)
        rel.load(self)
        rel.model.init_schema(args)
        filter_by, order_by = rel.model.get_filter_by(
            args), rel.model.get_order_by(args)
        where = None
        if 'where' in kwargs:
            where = kwargs['where'](self.rec, rel.model.rec)
        query = select_related(rel,
                               rec['id'],
                               filter_by=filter_by,
                               order_by=order_by,
                               offset=args.page.offset,
                               limit=args.page.limit,
                               search_term=search_term,
                               where=where)
        log_query(query)
        if rel.cardinality in (Cardinality.ONE_TO_ONE,
                               Cardinality.MANY_TO_ONE):
            result = await pg.fetchrow(query)
            data = dict(result) if result is not None else None
        else:
            data = {rec['id']: dict(rec) for rec in await pg.fetch(query)}
            data = list(data.values())
            await rel.model.set_meta(args.page.limit,
                                     rec['id'],
                                     rel,
                                     filter_by=filter_by,
                                     search_term=search_term,
                                     where=where)
            rel.model.check_size(args, data)
        await rel.model.fetch_included(data, args)
        return rel.model.response(data)