Conversation
| sort_by: Literal['created_at', 'last_run_started_at'] | None = 'created_at', | ||
| timeout: Timeout = 'medium', | ||
| ) -> ListOfActors: | ||
| ) -> IterablePageOfActors: |
There was a problem hiding this comment.
Naming parity with JS is not 100% as there it was possible to use generics, but the typing there allows more flexibility.
JS name:
PaginatedIterator<ActorCollectionListItem>
775d3ca to
fec16e9
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #769 +/- ##
========================================
Coverage 95.92% 95.92%
========================================
Files 48 50 +2
Lines 5226 5600 +374
========================================
+ Hits 5013 5372 +359
- Misses 213 228 +15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
|
||
|
|
||
| @dataclass | ||
| class IterablePageOfActorsAsync(AwaitablePage[ActorShort], AsyncIterableOf[ActorShort]): ... |
There was a problem hiding this comment.
I tried different approaches with generics, but this turned out to be the best approach:
- It truly describes all the attributes
- It has both static safety, and it benefits from run-time validation when these dataclasses are created from Pydantic models
- It has as little code duplication as possible due to the autogenerated composable init of
dataclass - It gives a clear overview of which endpoints are "weird" - not having the normal pagination metadata
| STORAGE_CLIENTS = DATASET_CLIENTS | RQ_CLIENTS | KVS_CLIENTS | ||
| ALL_CLIENTS = COLLECTION_CLIENTS | NO_OPTIONS_CLIENTS | STORAGE_CLIENTS | ||
|
|
||
| TEST_CASES = ( |
There was a problem hiding this comment.
Test setup is somewhat complex and this is due to test code being nearly identical for each endpoint, but different endpoints not being consistent in which pagination it supports (different params and different pagination metadata).
Through this parameter definition, over 200 test cases is generated based on just a few actual tests
|
|
||
| @pytest.fixture(autouse=True) | ||
| def _relax_item_validation() -> Any: | ||
| """Relax only the element type of `items` on paginated list models for the test run. |
There was a problem hiding this comment.
These tests are only about pagination logic and constructing each item correctly is totally out of scope, so in all Pydantic models the items: list[whatever] validation is replaced by items: list[dict]
vdusek
left a comment
There was a problem hiding this comment.
Sorry, but I'm really not sure about merging pagination and iteration into a single list method. These are two distinct use cases - you either want a page (with metadata), or you want to stream items - and choosing the right method is a way to express that intent.
I would say this also goes against the single responsibility principle - the method now serves two different concerns, which makes the API worse in terms of DX.
If we look beyond the interface and go to the implementation, it becomes even worse. The current approach introduces a 14 IterablePageOf... classes plus mixins just to make the typing work. The return type carries both responsibilities; the async variant is no longer a coroutine but a lazy awaitable-iterable wrapper, and each resource client ends up using a closure-over-callback pattern (where there used to be two simple lines before).
Could we keep list() for pagination and iterate() for iteration? It's a more intuitive interface; callers don't have to learn the quirks of a hybrid object, it will be more understandable for type checkers, and the implementation would be significantly simpler without this mess.
| import IterateItemsSyncExample from '!!raw-loader!./code/08_iterate_items_sync.py'; | ||
|
|
||
|
|
||
| Most methods named `list` or `list_something` in the Apify client return a <ApiLink to="class/ListPage">`ListPage`</ApiLink> object. This object provides a consistent interface for working with paginated data and includes the following properties: |
There was a problem hiding this comment.
Do we still have ListPage? Could you ask Claude to also review and update the docs?
If we agree on having it split into two methods in Python client, then it will be way simpler and cleaner internally. |
Well, in https://github.com/apify/apify-client-js, we have a Our current doctrine is pretty much "1:1 parity unless we have a clearly superior approach that we want to eventually use in the other flavor as well". Also, we mostly tend to sacrifice maintainability if it means that the API will become easier to use. Thus the one thing to consider from @vdusek response is if returning a "polymorphic" object that is both an async iterable and a simple promise is strictly worse DX than having two distinct methods for each list-shaped endpoint. If it is, then we should also decide if this is something that we'd like to adjust in the JS version or if there is a compelling argument for letting the JS version and the Python version diverge. |
Description
Example usage
Issues
Testing
Checklist