-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[WIP] implement first-class paging support #740
Conversation
I'll pause here before going further and see if people have feedback on this approach. |
public int? StartPage { get; set; } | ||
public int? PageCount { get; set; } | ||
public int? PageSize { get; set; } | ||
public string Accepts { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might also be how we support different media types in the response without hard-coding 💩 everywhere. Added a task to explore this further.
Went back and forth with @JakeGinnivan about this API stuff, and he pointed out that this change will make the task returned from
I don't think this is impossible to do within our current infrastructure, but right now I need to be mindful of this. |
{ | ||
public static class CustomAwaiter | ||
{ | ||
public static TaskAwaiter<IReadOnlyList<T>> GetAwaiter<T>(this IDeferredRequest<T> request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I could 🔥 this awaiter and make the user call .ToTask()
for these methods.
It's less magical, but takes away the hot/cold issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a breaking change. The API itself has changed from hot to cold and existing code now needs to .ToTask()
Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm not sure I can magic up a type here that's both Hot and also modifiable using WithOptions.
So I'm weighing up varying degrees of breaking changes to see how they feel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API itself has changed from hot to cold and existing code now needs to .ToTask()
Yes, any existing API calls will no longer magically work with await
. So you're updating anyway.
Been thinking on different approaches. Closing this one for now. |
🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵 WORK IN PROGRESS 🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵
🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵 🌵 DO NOT MERGE 🌵 🌵 🌵🌵 🌵 🌵 🌵 🌵 🌵
I'd been noodling around with how to tackle paging in Octokit, and here's my first attempt at a solution.
The Problem
By default, if there's new data to fetch, the Octokit client will go and fetch it. Thanks to the pagination APIs, this is rather easy to do. For my use case, that's been Good Enough. But I'm at the point where I'd like to do things like:
Some endpoints support other sorts of filtering (for example, issues search has a
since
parameter. I'm not worrying about those here - this approach should work for all endpoints that return arrays of results.This isn't something I've focused on given I spent most of my days working with desktop apps, but I know other platforms would love having this level of control to conserve data.
And we have #259 and #625 as open issues, so why not do try and resolve this?
The Approach
You can await any type in C# with the presence of
async
/await
- and it's pretty easy once you know how to get started. For backwards-compatibility, I didn't want to force you to update your code - if you didn't opt-in to this behaviour, you should get the existing behaviour.So I started with this ideal API:
Instead of returning
Task<IReadOnlyList<T>>
here I'm returningILazyRequest<T>
- and it's this type that has the extra options necessary. These is how one can tweak the internal state of the request before it executes.To get the
await
magic to work, you need to define a public static method in a static type that takes your type as an extension method and returns aTaskAwaiter<T>
.This lives under the
Octokit
namespace, so it should Just Work with existing users.ToTask()
just defers to the existing behaviour, but this gives me the hook necessary to inject the additional parameters before executing the request.There's also some enhancements necessary to
IApiPagination
to allow clients to opt-out to paging early. I should be able to extract this dependency fromApiConnection
by the time this is done, so I'm fine with changing that behaviour around.This is the API I've settled on:
Limitations
I need to think about how to do this with the Observable-based APIs - currently we just cheat and transform the Task-based API, but because we surface anIObservable<T>
we'd need to do something similar here. Plus there are extension methods likeSkip
andTake
available that we should be respecting (and not fetching all the records in the first place). Perhaps this is when the two libraries diverge, implementation-wise 😢EDIT: we actually use a different implementation inside the
GetAll
s for the observable clients -GetAndFlattenAllPages
- so I'm gonna put this question aside for another day.TODO
ILazyRequest
->IDeferredRequest
RepositoriesClient
migration[ ] ponder on how to do this with👢 punt to another timeOctokit.Reactive