Esempio n. 1
0
def test_api_paging_extension():
    item_collection = request(ITEM_COLLECTION)
    item_collection["links"] += [
        {
            "title": "next page",
            "rel": "next",
            "method": "GET",
            "href": "http://next"
        },
        {
            "title": "previous page",
            "rel": "previous",
            "method": "POST",
            "href": "http://prev",
            "body": {
                "key": "value"
            },
        },
    ]
    model = ItemCollection(**item_collection)
    links = model.to_dict()["links"]

    # Make sure we can mix links and pagination links
    normal_link = Link(**links[0])
    assert normal_link.rel == "self"
    next_link = PaginationLink(**links[1])
    assert next_link.rel == "next"
    previous_link = PaginationLink(**links[2])
    assert previous_link.rel == "previous"
    assert previous_link.body == {"key": "value"}
Esempio n. 2
0
def test_api_invalid_paging_link():
    # Invalid rel type
    with pytest.raises(ValidationError):
        PaginationLink(rel="self", method="GET", href="http://next")

    # Invalid method
    with pytest.raises(ValidationError):
        PaginationLink(rel="next", method="DELETE", href="http://next")
Esempio n. 3
0
 def link_prev(self) -> PaginationLink:
     """Create link for previous page."""
     if self.prev is not None:
         method = self.request.method
         if method == "GET":
             href = merge_params(self.url, {"token": f"prev:{self.prev}"})
             return PaginationLink(
                 rel=Relations.previous,
                 type=MimeTypes.json,
                 method=method,
                 href=href,
             )
         if method == "POST":
             body = self.request.postbody
             body["token"] = f"prev:{self.prev}"
             return PaginationLink(
                 rel=Relations.previous,
                 type=MimeTypes.json,
                 method=method,
                 href=f"{self.request.url}",
                 body=body,
             )
Esempio n. 4
0
 def link_next(self) -> PaginationLink:
     """Create link for next page."""
     if self.next is not None:
         method = self.request.method
         if method == "GET":
             href = merge_params(self.url, {"token": f"next:{self.next}"})
             link = PaginationLink(
                 rel=Relations.next,
                 type=MimeTypes.json,
                 method=method,
                 href=href,
             )
             return link
         if method == "POST":
             body = self.request.postbody
             body["token"] = f"next:{self.next}"
             return PaginationLink(
                 rel=Relations.next,
                 type=MimeTypes.json,
                 method=method,
                 href=f"{self.request.url}",
                 body=body,
             )
Esempio n. 5
0
    def post_search(self, search_request: STACSearch,
                    **kwargs) -> Dict[str, Any]:
        """POST search catalog."""
        with self.session.reader.context_session() as session:
            token = (self.get_token(search_request.token)
                     if search_request.token else False)
            query = session.query(self.item_table)

            # Filter by collection
            count = None
            if search_request.collections:
                query = query.join(self.collection_table).filter(
                    sa.or_(*[
                        self.collection_table.id == col_id
                        for col_id in search_request.collections
                    ]))

            # Sort
            if search_request.sortby:
                sort_fields = [
                    getattr(self.item_table.get_field(sort.field),
                            sort.direction.value)()
                    for sort in search_request.sortby
                ]
                sort_fields.append(self.item_table.id)
                query = query.order_by(*sort_fields)
            else:
                # Default sort is date
                query = query.order_by(self.item_table.datetime.desc(),
                                       self.item_table.id)

            # Ignore other parameters if ID is present
            if search_request.ids:
                id_filter = sa.or_(
                    *[self.item_table.id == i for i in search_request.ids])
                items = query.filter(id_filter).order_by(self.item_table.id)
                page = get_page(items,
                                per_page=search_request.limit,
                                page=token)
                if self.extension_is_enabled(ContextExtension):
                    count = len(search_request.ids)
                page.next = (self.insert_token(
                    keyset=page.paging.bookmark_next)
                             if page.paging.has_next else None)
                page.previous = (self.insert_token(
                    keyset=page.paging.bookmark_previous)
                                 if page.paging.has_previous else None)

            else:
                # Spatial query
                poly = None
                if search_request.intersects is not None:
                    poly = shape(search_request.intersects)
                elif search_request.bbox:
                    poly = ShapelyPolygon.from_bounds(*search_request.bbox)

                if poly:
                    filter_geom = ga.shape.from_shape(poly, srid=4326)
                    query = query.filter(
                        ga.func.ST_Intersects(self.item_table.geometry,
                                              filter_geom))

                # Temporal query
                if search_request.datetime:
                    # Two tailed query (between)
                    if ".." not in search_request.datetime:
                        query = query.filter(
                            self.item_table.datetime.between(
                                *search_request.datetime))
                    # All items after the start date
                    if search_request.datetime[0] != "..":
                        query = query.filter(self.item_table.datetime >=
                                             search_request.datetime[0])
                    # All items before the end date
                    if search_request.datetime[1] != "..":
                        query = query.filter(self.item_table.datetime <=
                                             search_request.datetime[1])

                # Query fields
                if search_request.query:
                    for (field_name, expr) in search_request.query.items():
                        field = self.item_table.get_field(field_name)
                        for (op, value) in expr.items():
                            query = query.filter(op.operator(field, value))

                if self.extension_is_enabled(ContextExtension):
                    count_query = query.statement.with_only_columns(
                        [func.count()]).order_by(None)
                    count = query.session.execute(count_query).scalar()
                page = get_page(query,
                                per_page=search_request.limit,
                                page=token)
                # Create dynamic attributes for each page
                page.next = (self.insert_token(
                    keyset=page.paging.bookmark_next)
                             if page.paging.has_next else None)
                page.previous = (self.insert_token(
                    keyset=page.paging.bookmark_previous)
                                 if page.paging.has_previous else None)

            links = []
            if page.next:
                links.append(
                    PaginationLink(
                        rel=Relations.next,
                        type="application/geo+json",
                        href=f"{kwargs['request'].base_url}search",
                        method="POST",
                        body={"token": page.next},
                        merge=True,
                    ))
            if page.previous:
                links.append(
                    PaginationLink(
                        rel=Relations.previous,
                        type="application/geo+json",
                        href=f"{kwargs['request'].base_url}search",
                        method="POST",
                        body={"token": page.previous},
                        merge=True,
                    ))

            response_features = []
            filter_kwargs = {}
            if self.extension_is_enabled(FieldsExtension):
                if search_request.query is not None:
                    query_include: Set[str] = set([
                        k if k in Settings.get().indexed_fields else
                        f"properties.{k}" for k in search_request.query.keys()
                    ])
                    if not search_request.field.include:
                        search_request.field.include = query_include
                    else:
                        search_request.field.include.union(query_include)

                filter_kwargs = search_request.field.filter_fields

            xvals = []
            yvals = []
            for item in page:
                item.base_url = str(kwargs["request"].base_url)
                item_model = schemas.Item.from_orm(item)
                xvals += [item_model.bbox[0], item_model.bbox[2]]
                yvals += [item_model.bbox[1], item_model.bbox[3]]
                response_features.append(item_model.to_dict(**filter_kwargs))

        try:
            bbox = (min(xvals), min(yvals), max(xvals), max(yvals))
        except ValueError:
            bbox = None

        context_obj = None
        if self.extension_is_enabled(ContextExtension):
            context_obj = {
                "returned": len(page),
                "limit": search_request.limit,
                "matched": count,
            }

        return {
            "type": "FeatureCollection",
            "context": context_obj,
            "features": response_features,
            "links": links,
            "bbox": bbox,
        }
Esempio n. 6
0
    def item_collection(self,
                        id: str,
                        limit: int = 10,
                        token: str = None,
                        **kwargs) -> ItemCollection:
        """Read an item collection from the database."""
        with self.session.reader.context_session() as session:
            collection_children = (session.query(self.item_table).join(
                self.collection_table).filter(
                    self.collection_table.id == id).order_by(
                        self.item_table.datetime.desc(), self.item_table.id))
            count = None
            if self.extension_is_enabled(ContextExtension):
                count_query = collection_children.statement.with_only_columns(
                    [func.count()]).order_by(None)
                count = collection_children.session.execute(
                    count_query).scalar()
            token = self.get_token(token) if token else token
            page = get_page(collection_children,
                            per_page=limit,
                            page=(token or False))
            # Create dynamic attributes for each page
            page.next = (self.insert_token(keyset=page.paging.bookmark_next)
                         if page.paging.has_next else None)
            page.previous = (self.insert_token(
                keyset=page.paging.bookmark_previous)
                             if page.paging.has_previous else None)

            links = []
            if page.next:
                links.append(
                    PaginationLink(
                        rel=Relations.next,
                        type="application/geo+json",
                        href=
                        f"{kwargs['request'].base_url}collections/{id}/items?token={page.next}&limit={limit}",
                        method="GET",
                    ))
            if page.previous:
                links.append(
                    PaginationLink(
                        rel=Relations.previous,
                        type="application/geo+json",
                        href=
                        f"{kwargs['request'].base_url}collections/{id}/items?token={page.previous}&limit={limit}",
                        method="GET",
                    ))

            response_features = []
            for item in page:
                item.base_url = str(kwargs["request"].base_url)
                response_features.append(schemas.Item.from_orm(item))

            context_obj = None
            if self.extension_is_enabled(ContextExtension):
                context_obj = {
                    "returned": len(page),
                    "limit": limit,
                    "matched": count
                }

            return ItemCollection(
                type="FeatureCollection",
                context=context_obj,
                features=response_features,
                links=links,
            )
Esempio n. 7
0
    def item_collection(
        self, id: str, limit: int = 10, token: str = None, **kwargs
    ) -> ItemCollection:
        """Read an item collection from the database"""
        try:
            collection_children = (
                self.reader_session.query(self.table)
                .join(self.collection_table)
                .filter(self.collection_table.id == id)
                .order_by(self.table.datetime.desc(), self.table.id)
            )
            count = None
            if config.settings.api_extension_is_enabled(config.ApiExtensions.context):
                count_query = collection_children.statement.with_only_columns(
                    [func.count()]
                ).order_by(None)
                count = collection_children.session.execute(count_query).scalar()
            token = self.pagination_client.get(token) if token else token
            page = get_page(collection_children, per_page=limit, page=(token or False))
            # Create dynamic attributes for each page
            page.next = (
                self.pagination_client.insert(keyset=page.paging.bookmark_next)
                if page.paging.has_next
                else None
            )
            page.previous = (
                self.pagination_client.insert(keyset=page.paging.bookmark_previous)
                if page.paging.has_previous
                else None
            )
        except errors.NotFoundError:
            raise
        except Exception as e:
            logger.error(e, exc_info=True)
            raise errors.DatabaseError(
                "Unhandled database error when getting collection children"
            )

        links = []
        if page.next:
            links.append(
                PaginationLink(
                    rel=Relations.next,
                    type="application/geo+json",
                    href=f"{kwargs['request'].base_url}collections/{id}/items?token={page.next}&limit={limit}",
                    method="GET",
                )
            )
        if page.previous:
            links.append(
                PaginationLink(
                    rel=Relations.previous,
                    type="application/geo+json",
                    href=f"{kwargs['request'].base_url}collections/{id}/items?token={page.previous}&limit={limit}",
                    method="GET",
                )
            )

        response_features = []
        for item in page:
            item.base_url = str(kwargs["request"].base_url)
            response_features.append(schemas.Item.from_orm(item))

        context_obj = None
        if config.settings.api_extension_is_enabled(ApiExtensions.context):
            context_obj = {"returned": len(page), "limit": limit, "matched": count}

        return ItemCollection(
            type="FeatureCollection",
            context=context_obj,
            features=response_features,
            links=links,
        )