예제 #1
0
    async def item_collection(self,
                              id: str,
                              limit: int = 10,
                              token: str = None,
                              **kwargs) -> ItemCollection:
        """Get all items from a specific collection.

        Called with `GET /collections/{collectionId}/items`

        Args:
            id: id of the collection.
            limit: number of items to return.
            token: pagination token.

        Returns:
            An ItemCollection.
        """
        # If collection does not exist, NotFoundError wil be raised
        await self.get_collection(id, **kwargs)

        req = PgstacSearch(collections=[id], limit=limit, token=token)
        item_collection = await self._search_base(req, **kwargs)
        links = await CollectionLinks(collection_id=id,
                                      request=kwargs["request"]).get_links(
                                          extra_links=item_collection["links"])
        item_collection["links"] = links
        return item_collection
예제 #2
0
    async def _search_base(self, search_request: PgstacSearch,
                           **kwargs) -> Dict[str, Any]:
        """Cross catalog search (POST).

        Called with `POST /search`.

        Args:
            search_request: search request parameters.

        Returns:
            ItemCollection containing items which match the search criteria.
        """
        request = kwargs["request"]
        pool = request.app.state.readpool

        # pool = kwargs["request"].app.state.readpool
        req = search_request.json(exclude_none=True)

        async with pool.acquire() as conn:
            q, p = render(
                """
                SELECT * FROM search(:req::text::jsonb);
                """,
                req=req,
            )
            items = await conn.fetchval(q, *p)
        next = items.pop("next", None)
        prev = items.pop("prev", None)
        collection = ItemCollection.construct(**items)
        cleaned_features = []
        if collection.features is None or len(collection.features) == 0:
            raise NotFoundError("No features found")

        for feature in collection.features:
            feature = Item.construct(**feature)
            if "links" not in search_request.fields.exclude:
                links = await ItemLinks(
                    collection_id=feature.collection,
                    item_id=feature.id,
                    request=request,
                ).get_links()
                feature.links = links
                exclude = search_request.fields.exclude
                if len(exclude) == 0:
                    exclude = None
                include = search_request.fields.include
                if len(include) == 0:
                    include = None
                feature = feature.dict(exclude_none=True, )
            cleaned_features.append(feature)
            collection.features = cleaned_features
        collection.links = await PagingLinks(
            request=request,
            next=next,
            prev=prev,
        ).get_links()
        return collection
예제 #3
0
    async def get_item(self, item_id: str, collection_id: str,
                       **kwargs) -> ORJSONResponse:
        """Get item by id.

        Called with `GET /collections/{collectionId}/items/{itemId}`.

        Args:
            id: Id of the item.

        Returns:
            Item.
        """
        req = PgstacSearch(ids=[item_id], limit=1)
        collection = await self._search_base(req, **kwargs)
        return ORJSONResponse(collection.features[0])
예제 #4
0
    async def get_item(self, item_id: str, collection_id: str,
                       **kwargs) -> Item:
        """Get item by id.

        Called with `GET /collections/{collectionId}/items/{itemId}`.

        Args:
            id: Id of the item.

        Returns:
            Item.
        """
        # If collection does not exist, NotFoundError wil be raised
        await self.get_collection(collection_id, **kwargs)

        req = PgstacSearch(ids=[item_id], limit=1)
        item_collection = await self._search_base(req, **kwargs)
        if not item_collection["features"]:
            raise NotFoundError(f"Collection {collection_id} does not exist.")

        return Item(**item_collection["features"][0])
예제 #5
0
    async def item_collection(self,
                              id: str,
                              limit: int = 10,
                              token: str = None,
                              **kwargs) -> ORJSONResponse:
        """Get all items from a specific collection.

        Called with `GET /collections/{collectionId}/items`

        Args:
            id: id of the collection.
            limit: number of items to return.
            token: pagination token.

        Returns:
            An ItemCollection.
        """
        req = PgstacSearch(collections=[id], limit=limit, token=token)
        collection = await self._search_base(req, **kwargs)
        links = await CollectionLinks(
            collection_id=id,
            request=kwargs["request"]).get_links(extra_links=collection.links)
        collection.links = links
        return ORJSONResponse(collection.dict(exclude_none=True))
예제 #6
0
    async def _search_base(self, search_request: PgstacSearch,
                           **kwargs: Any) -> ItemCollection:
        """Cross catalog search (POST).

        Called with `POST /search`.

        Args:
            search_request: search request parameters.

        Returns:
            ItemCollection containing items which match the search criteria.
        """
        items: Dict[str, Any]

        request: Request = kwargs["request"]
        pool = request.app.state.readpool

        # pool = kwargs["request"].app.state.readpool
        req = search_request.json(exclude_none=True)

        try:
            async with pool.acquire() as conn:
                q, p = render(
                    """
                    SELECT * FROM search(:req::text::jsonb);
                    """,
                    req=req,
                )
                items = await conn.fetchval(q, *p)
        except InvalidDatetimeFormatError:
            raise InvalidQueryParameter(
                f"Datetime parameter {search_request.datetime} is invalid.")

        next: Optional[str] = items.pop("next", None)
        prev: Optional[str] = items.pop("prev", None)
        collection = ItemCollection(**items)
        cleaned_features: List[Item] = []

        for feature in collection.get("features") or []:
            feature = Item(**feature)
            if (search_request.fields.exclude is None
                    or "links" not in search_request.fields.exclude):
                # TODO: feature.collection is not always included
                # This code fails if it's left outside of the fields expression
                # I've fields extension updated test cases to always include feature.collection
                feature["links"] = await ItemLinks(
                    collection_id=feature["collection"],
                    item_id=feature["id"],
                    request=request,
                ).get_links(extra_links=feature.get("links"))

                exclude = search_request.fields.exclude
                if exclude and len(exclude) == 0:
                    exclude = None
                include = search_request.fields.include
                if include and len(include) == 0:
                    include = None
            cleaned_features.append(feature)

        collection["features"] = cleaned_features
        collection["links"] = await PagingLinks(
            request=request,
            next=next,
            prev=prev,
        ).get_links()
        return collection
예제 #7
0
    async def get_search(
        self,
        collections: Optional[List[str]] = None,
        ids: Optional[List[str]] = None,
        bbox: Optional[List[NumType]] = None,
        datetime: Optional[Union[str, datetime]] = None,
        limit: Optional[int] = 10,
        query: Optional[str] = None,
        token: Optional[str] = None,
        fields: Optional[List[str]] = None,
        sortby: Optional[str] = None,
        **kwargs,
    ) -> ItemCollection:
        """Cross catalog search (GET).

        Called with `GET /search`.

        Returns:
            ItemCollection containing items which match the search criteria.
        """
        # Parse request parameters
        base_args = {
            "collections": collections,
            "ids": ids,
            "bbox": bbox,
            "limit": limit,
            "token": token,
            "query": orjson.loads(query) if query else query,
        }
        if datetime:
            base_args["datetime"] = datetime

        if sortby:
            # https://github.com/radiantearth/stac-spec/tree/master/api-spec/extensions/sort#http-get-or-post-form
            sort_param = []
            for sort in sortby:
                sortparts = re.match(r"^([+-]?)(.*)$", sort)
                if sortparts:
                    sort_param.append({
                        "field":
                        sortparts.group(2).strip(),
                        "direction":
                        "desc" if sortparts.group(1) == "-" else "asc",
                    })
            base_args["sortby"] = sort_param

        if fields:
            includes = set()
            excludes = set()
            for field in fields:
                if field[0] == "-":
                    excludes.add(field[1:])
                elif field[0] == "+":
                    includes.add(field[1:])
                else:
                    includes.add(field)
            base_args["fields"] = {"include": includes, "exclude": excludes}

        # Do the request
        try:
            search_request = PgstacSearch(**base_args)
        except ValidationError:
            raise HTTPException(status_code=400,
                                detail="Invalid parameters provided")
        return await self.post_search(search_request,
                                      request=kwargs["request"])