Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STAC Client pagination support #327

Merged
merged 5 commits into from
May 24, 2021

Conversation

pomadchin
Copy link
Collaborator

@pomadchin pomadchin commented May 20, 2021

Overview

This PR refactors StacClient. All methods that returned Lists return fs2.Streams now. These changes allowed SttpStacClient to support pagination 🎉 🎉 🎉

All changes are done to keep the interface as simple as it is possible, see how it can be combined via Apply2K here

Checklist

  • New tests have been added or existing tests have been modified
  • Changelog updated (please use chan)

Other info

// that's how we can use tofu.Mid to add tracing both on F and G
// it is really sad that we have A in our interfaces which was done to avoid copy pasting in JVM and JS clients :/
implicit class MidOps3[U[_[_], _[_], _], F[_], G[_], A](val u: U[F, G, A]) extends AnyVal {
  def attach2(mf: U[Mid[F, *], G, A])(mg: U[F, Mid[G, *], A])(implicit af: ApplyK[U[F, *[_], A]], ag: ApplyK[U[*[_], G, A]]): U[F, G, A] =
    Mid.attach[U[*[_], G, A], F](mf)(Mid.attach[U[F, *[_], A], G](mg)(u))
}

// usage
client.attach2(midF)(midG) 

or a more traditional attach semantics:

implicit class MidOps3Tuple[U[_[_], _[_], _], F[_], G[_], A](val mfg: (U[Mid[F, *], G, A], U[F, Mid[G, *], A])) extends AnyVal {
  def attach(u: U[F, G, A])(implicit af: ApplyK[U[F, *[_], A]], ag: ApplyK[U[*[_], G, A]]): U[F, G, A] =
    Mid.attach[U[*[_], G, A], F](mfg._1)(Mid.attach[U[F, *[_], A], G](mfg._2)(u))
}

// usage 
(midF, midG) attach client

Closes #198
Closes #324
Closes #326
Closes #316
Closes #314

@pomadchin pomadchin force-pushed the feature/client-streams branch 4 times, most recently from 1cabc1c to 23a050e Compare May 20, 2021 01:59
@pomadchin pomadchin force-pushed the feature/client-streams branch from 30716e3 to 7c238b1 Compare May 20, 2021 04:27
@pomadchin pomadchin changed the title STAC Client pagination support [WIP] STAC Client pagination support May 20, 2021
@pomadchin pomadchin changed the title [WIP] STAC Client pagination support STAC Client pagination support May 20, 2021
@pomadchin pomadchin requested a review from jisantuc May 20, 2021 13:56
@pomadchin pomadchin changed the title STAC Client pagination support [WIP] STAC Client pagination support May 20, 2021
@pomadchin pomadchin removed the request for review from jisantuc May 20, 2021 15:25
@pomadchin pomadchin force-pushed the feature/client-streams branch 3 times, most recently from c4d8a6e to ccbd9ed Compare May 20, 2021 21:56
@pomadchin pomadchin changed the title [WIP] STAC Client pagination support STAC Client pagination support May 20, 2021
@pomadchin pomadchin force-pushed the feature/client-streams branch 2 times, most recently from fbab985 to fe67718 Compare May 20, 2021 22:13
Comment on lines 4 to 7
type SttpStacClient[F[_]] = SttpStacClientF[F, SearchFilters]
type StacClient[F[_]] = StacClientF[F, SearchFilters]
type StreamingClient[F[_]] = StreamingStacClientF[F, fs2.Stream[F, *], SearchFilters]
type StreamingStacClient[F[_], G[_]] = StreamingStacClientF[F, G, SearchFilters]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still should be type aliases, traits would not work since they add children into the traits hierarchy.

def item(collectionId: NonEmptyString, itemId: NonEmptyString): F[StacItem]
def itemCreate(collectionId: NonEmptyString, item: StacItem): F[StacItem]
def collectionCreate(collection: StacCollection): F[StacCollection]
}

trait StreamingStacClientF[F[_], G[_], S] extends StacClientF[F, S] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

G here is a separate parameter to make it independent from F.

/** [[Sync]] instance defined for Either[Throwable, *].
* It is required (sadly) to derive [[fs2.Stream.Compiler]] which is necessary for the [[fs2.Stream.compile]] function.
*/
implicit val eitherSync: Sync[Either[Throwable, *]] = new Sync[Either[Throwable, *]] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the default synchronous Either backend to use the same tests set for the Scala JS backend. (c)

@pomadchin pomadchin force-pushed the feature/client-streams branch from 9a1f157 to e85ee2f Compare May 21, 2021 15:18
Comment on lines +53 to +54
val items = body.flatMap(_.hcursor.downField("features").as[List[StacItem]]).liftTo[F]
val next = getNextLink(body).map(_.map(_ -> noPaginationBody))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can be better handled via #199 eventually

Copy link
Contributor

@jisantuc jisantuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mostly looks good -- can you let me know some testing instructions so I can try it out?

// the same filter would be used as a body for all pagination requests
val noPaginationBody = filter.map(paginationTokenLens.set(None)(_).asJson).getOrElse(emptyJson)
Stream
.unfoldLoopEval((baseUri.withPath("search"), initialBody)) { case (link, request) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pomadchin
Copy link
Collaborator Author

pomadchin commented May 21, 2021

@jisantuc you mean testing insturctions that are different from tests? There is also its usage example in GT Server. Sure:

// to run it you'd need the following dep
// "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats-ce2" % "3.3.4"

import com.azavea.stac4s.api.client.SttpStacClient
import cats.effect.IO
import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend
import sttp.client3.UriContext

import scala.concurrent.ExecutionContext

object Test {
  implicit val executionContext = ExecutionContext.global
  implicit val contextShift     = IO.contextShift(executionContext)
  implicit val timer            = IO.timer(executionContext)

  def main(args: Array[String]): Unit = {
    AsyncHttpClientCatsBackend
      .resource[IO]()
      .use { backend =>
        val client = SttpStacClient(backend, uri"https://franklin.nasa-hsi.azavea.com/")
        // any number here, or don't use take at all
        // try to pass params, fetch collections, items, etc
        client.search.take(95).compile.toList
      }
      .map(list => println(s"items count: ${list.map(_.id).distinct.length}"))
      .unsafeRunSync()
  }
}

@jisantuc
Copy link
Contributor

Yeah pretty much exactly that -- it's helpful to me to have a sense of the real use that the test is a tiny version of. Maybe that's not a great attitude about tests. Anyway I threw your example into this ammonite script and it worked great:

import $ivy.`com.azavea.stac4s::core:0.4.0-13-ge85ee2f-SNAPSHOT`
import $ivy.`com.azavea.stac4s::client:0.4.0-13-ge85ee2f-SNAPSHOT`
import $ivy.`com.softwaremill.sttp.client3::async-http-client-backend-cats-ce2:3.3.4`

import com.azavea.stac4s.api.client.SttpStacClient
import cats.effect.IO
import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend
import sttp.client3.UriContext

import scala.concurrent.ExecutionContext

object Test {
  implicit val executionContext = ExecutionContext.global
  implicit val contextShift     = IO.contextShift(executionContext)
  implicit val timer            = IO.timer(executionContext)

  def main(args: Array[String]): Unit = {
    AsyncHttpClientCatsBackend
      .resource[IO]()
      .use { backend =>
        val client = SttpStacClient(backend, uri"https://franklin.nasa-hsi.azavea.com/")
        // any number here, or don't use take at all
        // try to pass params, fetch collections, items, etc
        client.search.take(95).compile.toList
      }
      .map(list => println(s"items count: ${list.map(_.id).distinct.length}"))
      .unsafeRunSync()
  }
}

Test.main(Array.empty[String])

@pomadchin pomadchin merged commit b8eb735 into stac-utils:master May 24, 2021
@pomadchin pomadchin deleted the feature/client-streams branch May 24, 2021 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

STAC Client pagination support
2 participants