-
Notifications
You must be signed in to change notification settings - Fork 441
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Allow access to request metadata on success #371
Allow access to request metadata on success #371
Conversation
sigh Yeah. The way we handle keyword parameters in Python makes it really inconvenient to add new "special" parameters. We already have a few of those ( I would really rather avoid introducing new special parameters, esp. since this one as you pointed out would just be a configuration flag not directly related to the API. How about introducing a context manager for this? E.g.: with stripe.catch_responses() as r:
charge = stripe.Charge.retrieve("ch_...")
request_id = r[0].headers['Request-Id'] Basically, the context manager would collect the responses to all requests sent within the context. |
My concern with that approach is that to get your headers for any arbitrary response the consuming application needs to keep track of sequence number of the request which sounds like a pain. But then again I don't regularly develop asynchronous code in python - so maybe it is not a big deal. I personally think some type of options hash on the base Both of our approaches in java PR and in the the Node SDK issue intend on using some reference off the retrieved api model object. I was hoping we could do something similar here @dalan-stripe do you have any opinions about the UX regarding how to optionally enable responses upon requests? |
What if the context manager attached the response object to the resource object? with stripe.store_responses():
charge = stripe.Charge.retrieve("ch_...")
request_id = charge.response.headers['Request-Id'] (Just thinking aloud here, not sure how feasible this would be in practice.) |
@ob-stripe Yeah I like that much better. I think the name should be This way consumers can manage the scope of the optional change. I'm actually good with this approach |
👍 You might want to take a look at the implementation in stripe-ruby. Basically the HTTP client holds a reference to the last response (here), and the In our case we would do something similar, except that we'd copy the last response object onto the resource's |
I'm not so sure that this would be an appropriate usage of a context manager in Python. It also adds a lot of complexity to stripe.bulk_requests = True #signals that requests will be done in bulk
requests = [stripe.Charge.retrieve('ch_123'), stripe.Charge.retrieve('ch_124'), stripe.Charge.retrieve('ch_125')]
with requests.include_responses() as completed_requests:
for response, headers in completed_requests:
print(f"{response} - {headers}") It just seems very un-pythonic and potentially introduces problems for My vote is to just include it by default in the spirit of simplicity on the outermost requested resource. For example |
After giving it some more thought, I agree that a context manager may not be the best solution here. Also, we don't persist clients in the Python library (which is probably something we should fix too) which makes my proposed solution somewhat difficult to implement. I don't think we should start storing responses by default however, so as not to increase the memory footprint for users who don't need this feature. Also, we should not expose any requests-specific attributes as the library can use different HTTP clients. How about this:
We'll also probably have to define a custom JSON encoder to exclude |
Why do we want to optionally make the headers available? Why not just make them always available? |
@deontologician-stripe I think the concern is that if there's a consumer that happens to be making a ton of Stripe requests (and holding references to each response) that adding a reference to each of those response objects we deserialize and return to the consumer could cause memory bloat. @ob-stripe can elaborate if I'm off. |
As long as you're not holding references to all of your objects, it shouldn't be too much bloat. I think this is probably a case of premature optimization. In any case I agree with @dalan-stripe that having a context manager just for this option is not great ergonomics. A global module flag would be a good option, but ultimately I don't think this feature warrants a flag to turn it off since it's not going to cause most people any problems |
So, it turns out that this feature has already existed in stripe-php for a little over 2 years: stripe/stripe-php#206. In light of the arguments above and in the interest of feature parity and implementation consistency across our libraries, I recommend that:
We'd also need to update the pending Java implementation accordingly. |
d9f3976
to
63a4916
Compare
@ob-stripe sounds good. I've updated the PR to go in this direction. Only change is a little different is that I don't think |
Nice! Good call. 👍 I'm not a big fan of storing the Instead, I think This would also bring the implementation closer to stripe-ruby's:
wdyt? |
fair I think the modifications in the unit test reflect this awkwardness. I think the reason I did it this way was to get around having to make a bunch of changes all over the API resource classes which use the requestor interface directly :-/ |
# the raw API response information | ||
stripe_response = None | ||
|
||
if isinstance(resp, stripe.stripe_response.StripeResponse): |
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.
@ob-stripe I'm able to reduce the amount of code change needed from changing APIRequestor.request
by doing this little awkward bit. Essentially unpacking the response body dict inside convert_to_stripe_object
.
APIRequestor.request
's only real consumer is convert_to_stripe_object
we only need to change it here.
wdyt?
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, that sounds reasonable and less annoying than having to change all the call sites. It also maintains the existing behavior of convert_to_stripe_object
which is nice even if it's an internal function.
@ob-stripe I updated the PR with a similar approach to what you mention. The exception is instead of calling |
b423683
to
c96d954
Compare
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.
Requested one small change, but I think we'll be ready to merge afterwards. Thanks for bearing with me :)
stripe/api_requestor.py
Outdated
@@ -357,7 +358,8 @@ def interpret_response(self, rbody, rcode, rheaders): | |||
rbody, rcode, rheaders) | |||
if not (200 <= rcode < 300): | |||
self.handle_error_response(rbody, rcode, resp, rheaders) | |||
return resp | |||
|
|||
return StripeResponse(rbody, rcode, rheaders) |
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.
One minor optimization is that you could instantiate the StripeResponse
in the try
clause, to avoid parsing the JSON twice.
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.
✅ done!
# the raw API response information | ||
stripe_response = None | ||
|
||
if isinstance(resp, stripe.stripe_response.StripeResponse): |
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, that sounds reasonable and less annoying than having to change all the call sites. It also maintains the existing behavior of convert_to_stripe_object
which is nice even if it's an internal function.
77b716b
to
5177762
Compare
5177762
to
adf3fb3
Compare
Okay, this looks good to me! Thanks for making all the changes @sedouard. @brandur-stripe, mind taking a quick look as well? |
Sorry for the delay guys! And nice one @sedouard, this looks great. Huge +1 on building out a custom This is a bit of a nit, but do you think we could introduce a basic unit test suite for Otherwise, LGTM. Thanks again. |
Paging devs, is this getting merged? |
Yeah, we should get this in. @sedouard Mind taking a look at the comment above? Thanks! |
Yeah sorry guys for letting this PR hang out! I'll get this buttoned up in the next couple days. I got caught up with some other hi-pri work. |
@brandur-stripe I added new tests for |
Looks great! Thanks @stevene-stripe! |
Released as 1.77.0. |
Thanks! :) |
Hey, am I crazy or should this work? customer = stripe_customer.api_retrieve()
customer.account_balance -= 250
customer.save(idempotency_key=ik)
credit_request_id = customer.last_response.request_id # Breaks! last_response is None |
@jleclanche You're not crazy! Looks like the current implementation does not work well with Mind opening a new issue for this? I think the fix should be fairly simple. |
Sure, #393 |
Hey @ob-stripe this is my first stab at getting response headers out of the python bindings.
I'm trying to follow the same pattern by including a
response
resource to the returned API models.However one tricky part is understanding where the best spot is to place an optional flag to include the
response
field on returned API resources. Each API request method contains aparams
parameter that directly maps to the API's URL parameters. Placing some kind ofinclude_response
flag here would stick out since it won't be going to the API.Another thought was using a static variable somewhere and checking that variable within the
api_requestor
to include theresponse
or not.Do you have any opinions here?