-
Notifications
You must be signed in to change notification settings - Fork 92
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
Planet sync client #1049
base: main
Are you sure you want to change the base?
Planet sync client #1049
Conversation
Nice work! I'm definitely a big 👍 on the single Planet class. I did a small test listing my searches and then concurrently executing each search using both sync and async (threadpool and queue/task approach) and found the performance to be essentially equivalent w/ both... I'm less keen on duplication of all the documentation and boiler plate in testing, though I understand this is due to function "color". One approach I just tested was using a metaclass to dynamically generate sync wrappers. The advantage of this is documentation is mirrored and there's much less code. One disadvantage is type hints aren't handled properly - the actual return types are correct but IDE doc shows up incorrectly... Would be happy to share this but I'm not satisfied with the lack of type hint propagation (and maybe this can be addressed?) but otherwise, beyond some trickery, it's simple and it works. Another approach we might consider is top-level "request objects" [1] - this way the interface to the request (the name of the operation and the arguments) can always be sync and the results are where dispatch can vary. This way the docs for the operation parameters, exceptions, narrative etc are all in one spot and not repeated and the only code variation is essentially the So
Advantage here is parity w/ SH API. Another approach might be to use a request object but require a specific client, e.g.
The downside is we still have the old client classes, docs, etc. to maintain for some period of time... [1] see |
Another approach - by extending DataClient and overriding every function (as sync and with altered type hints), we get the best of both (can inherit doc strings and get correct type hints) with less boilerplate... vscode at least makes the overriding part simpler but then there's still maintenance (adding an optional parameter, for example) and the boilerplate of testing though at least w/ some creativity, I'd guess we could reduce this some... |
I do like the idea of inheriting the docstrings where possible. If we can give ourselves a headstart on a simple MVP wrapper, with minimal duplication of what's already there, then that sounds good to me. The reason I chose to start with the explicit approach is because this is a standalone client and it shouldn't necessarily be a violation of DRY merely to reuse the descriptions (IMO). They will probably need to be modified independently of each other as changes are made. That being said I will see what I can come up with for sharing the docstrings, I should have some time to round off the week to try a couple approaches 👍 |
It occurs to me while going down the path of creating shared docstrings, that we might be needlessly coupling the sync and async implementations together in order to save space on the docstrings. Yes each method calls out to the async method, but the implementation of any method can be changed (if needed), optional args added, and async or sync-specific examples can be added to the docstrings. Examples are sorely needed and those can't really be shared. The one-time port is simple and all the docs and type hints are correct (and the grunt work has been done). Thoughts? |
add some orders methods as examples
the async session fixture was breaking sync tests in nox
c5dbea2
to
54758aa
Compare
"""Get order details by Order ID. | ||
|
||
Parameters: | ||
order_id: The ID of the order | ||
|
||
Returns: | ||
JSON description of the order | ||
|
||
Raises: | ||
planet.exceptions.ClientError: If order_id is not a valid UUID. | ||
planet.exceptions.APIError: On API error. | ||
""" |
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 docstring is exactly the same as the docstring for it's async counterpart, OrdersClient().get_order()
. I wonder if we could simplify things by applying the docstring from OrdersClient.get_order()
to this method so that the same docstring isn't defined in two places? By 'apply' I mean something like 'share' the docstring between the two methods rather than 'inherit' because the implementation in this MR doesn't follow a standard Python inheritance pattern; the OrdersClient
is passed to OrdersAPI
via the OrdersAPI._client
property. Here's an example of what I am thinking:
class OrdersClient:
def get_order(self):
"""Docstring for get_order()."""
pass
class OrdersAPI:
def get_order(self):
self.get_order.__doc__ = self._client.get_order.__doc__
# Add any additional docstring here
pass
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.
I tend to think the negatives outweigh the positives here. Readers of the code would not have a docstring to refer to in the usual place and we also would not be able to use examples in shared docstrings since the methods are called differently.
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.
Understood! FWIW I don't think dynamic vs statically defined docstrings should block this MR. Check out #1077 where I implemented a dynamic approach. If that dynamic approach passes review, we can merge to main and ship it as an enhancement at a later date.
@pytest.fixture(scope="module") | ||
def data_api(): | ||
return DataAPI(Session(), TEST_URL) |
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 sync DataAPI client here is instantiated and accessed directly from the DataAPI object, whereas the sync SubscriptionsAPI and OrdersAPI clients are instantiated and accessed from the Planet() wrapper class in their respective tests. Should the sync DataAPI tests follow the same pattern as the sync SubscriptionsAPI/OrdersAPI tests?
Proposed Changes:
Adds a
Planet
sync client that has feature parity with the existing async clients.Diff of User Interface
New behavior:
The
Planet
client has membersdata
,subscriptions
,orders
. Users can interact with these APIs without setting up individual clients or a Session.Example:
Minor changes from the async clients to the sync client:
get_results
andget_results_csv
methods, the sync client has one method that optionally acceptsformat="csv"
as a parameter. It defaults to the normal json output.At the risk of scope creep, one additional nice-to-have for an initial MVP sync client would be some simple dataclass/attrs based return types instead of dicts. That way it's easier for users to understand what they're getting back in e.g. a search or order.