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)
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)
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)
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)
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)
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)