I have just installed the requests library in my appengine app following the instructions at https://cloud.google.com/appengine/docs/standard/python/issue-requests . When making the following request (within the app or within the interactive console)
import requests
import requests_toolbelt.adapters.appengine
# Use the App Engine Requests adapter. This makes sure that Requests uses
# URLFetch.
requests_toolbelt.adapters.appengine.monkeypatch()
r = requests.get('https://api.github.com/events')
I get the following error
> Traceback (most recent call last):
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/tools/devappserver2/python/request_handler.py", line 226, in handle_interactive_request
> exec(compiled_code, self._command_globals)
> File "<string>", line 8, in <module>
> File "/Users/developer/Desktop/myapp-web/libraries/requests/api.py", line 72, in get
> return request('get', url, params=params, **kwargs)
> File "/Users/developer/Desktop/myapp-web/libraries/requests/api.py", line 58, in request
> return session.request(method=method, url=url, **kwargs)
> File "/Users/developer/Desktop/myapp-web/libraries/requests/sessions.py", line 523, in request
> resp = self.send(prep, **send_kwargs)
> File "/Users/developer/Desktop/myapp-web/libraries/requests/sessions.py", line 643, in send
> r = adapter.send(request, **kwargs)
> File "/Users/developer/Desktop/myapp-web/libraries/requests/adapters.py", line 440, in send
> timeout=timeout
> File "/Users/developer/Desktop/myapp-web/libraries/requests_toolbelt/adapters/appengine.py", line 172, in urlopen
> **response_kw)
> File "/Users/developer/Desktop/myapp-web/libraries/urllib3/contrib/appengine.py", line 149, in urlopen
> validate_certificate=self.validate_certificate,
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch.py", line 293, in fetch
> return rpc.get_result()
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
> return self.__get_result_hook(self)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch.py", line 413, in _get_fetch_result
> rpc.check_success()
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 579, in check_success
> self.__rpc.CheckSuccess()
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_rpc.py", line 157, in _WaitImpl
> self.request, self.response)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py", line 206, in MakeSyncCall
> self._MakeRealSyncCall(service, call, request, response)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py", line 255, in _MakeRealSyncCall
> request_pb.set_request(request.Encode())
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 103, in Encode
> self.Output(e)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 347, in Output
> self.OutputUnchecked(e)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch_service_pb.py", line 484, in OutputUnchecked
> out.putDouble(self.deadline_)
> File "/usr/local/y/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 592, in putDouble
> a.fromstring(struct.pack("<d", v))
> error: required argument is not a float
I have the following packages installed (along with dependencies) via pip
requests-2.16.3.dist-info
requests_toolbelt-0.8.0.dist-info
Wow, this looks like it involves a lot of GAE code here. @jonparrott any ideas?
Just a further comment. I did the same thing using the simple hello world app (set up as per https://cloud.google.com/appengine/docs/standard/python/quickstart ) to ensure there weren't any errors introduced by my own code. Same issue. I tested previous versions of requests, and the last version that seems to work was 2.16.1
I'll take a look at this first thing Tuesday when I get back in the office.
Thanks!
On Sat, May 27, 2017, 1:22 PM A notifications@github.com wrote:
Just a further comment. I did the same thing using the simple hello world
app (set up as per
https://cloud.google.com/appengine/docs/standard/python/quickstart ) to
ensure there weren't any errors introduced by my own code. Same issue.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/kennethreitz/requests/issues/4078#issuecomment-304474528,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAPUcwCsf7nzMQ_plRUJda_YkI3tUedOks5r-IYLgaJpZM4NodTG
.
Hey Guys!
I've spent some time investigating this issue. It's been caused by the refactoring of requests.packages in version 2.16.2.
The quick fix is to downgrade to requests 2.16.0.
What I've found being broken:
In urllib3/contrib/appengine.py at AppEngineManager._get_absolute_timeout() is not returning a number as expected.
It's because the Timeout class imported from urllib3.timeout is not the same as what it receives as a timeout object.
I've put a couple brutal prints in the function:
def _get_absolute_timeout(self, timeout):
print "\n\n", "AppEngineManager._get_absolute_timeout", "0000", self
print "\n\n", "AppEngineManager._get_absolute_timeout", "timeout", timeout
print "\n\n", "AppEngineManager._get_absolute_timeout", "timeout.__class__", timeout.__class__
print "\n\n", "AppEngineManager._get_absolute_timeout", "Timeout", Timeout
print "\n\n", "AppEngineManager._get_absolute_timeout", "Timeout.DEFAULT_TIMEOUT.__class__", Timeout.DEFAULT_TIMEOUT.__class__
print "\n\n", "AppEngineManager._get_absolute_timeout", "timeout is Timeout.DEFAULT_TIMEOUT", timeout is Timeout.DEFAULT_TIMEOUT
if timeout is Timeout.DEFAULT_TIMEOUT:
print "\n\n", "AppEngineManager._get_absolute_timeout", "2222"
return None # Defer to URLFetch's default.
if isinstance(timeout, Timeout):
print "\n\n", "AppEngineManager._get_absolute_timeout", "3333"
if timeout._read is not None or timeout._connect is not None:
warnings.warn(
"URLFetch does not support granular timeout settings, "
"reverting to total or default URLFetch timeout.",
AppEnginePlatformWarning)
print "\n\n", "AppEngineManager._get_absolute_timeout", "4444"
return timeout.total
print "\n\n", "AppEngineManager._get_absolute_timeout", "5555"
return timeout
When it's executed with requests 2.16.0, it gives the following output:
```AppEngineManager._get_absolute_timeout 0000
AppEngineManager._get_absolute_timeout timeout Timeout(connect=None, read=None, total=None)
AppEngineManager._get_absolute_timeout timeout.__class__
AppEngineManager._get_absolute_timeout Timeout
AppEngineManager._get_absolute_timeout Timeout.DEFAULT_TIMEOUT.__class__
AppEngineManager._get_absolute_timeout timeout is Timeout.DEFAULT_TIMEOUT False
AppEngineManager._get_absolute_timeout 3333
AppEngineManager._get_absolute_timeout 4444
It works as expected, returns an integer number.
But when it's executed with requests>=2.16.2, the same code returns:
AppEngineManager._get_absolute_timeout 0000
AppEngineManager._get_absolute_timeout timeout Timeout(connect=None, read=None, total=None)
AppEngineManager._get_absolute_timeout timeout.__class__
AppEngineManager._get_absolute_timeout Timeout
AppEngineManager._get_absolute_timeout Timeout.DEFAULT_TIMEOUT.__class__
AppEngineManager._get_absolute_timeout timeout is Timeout.DEFAULT_TIMEOUT False
AppEngineManager._get_absolute_timeout 5555
```
As you can see the timeout argument's class is != the Timeout class it's being compared to. Because of this isinstance(timeout, Timeout): returns False, so the function returns the timeout object instead of the integer value of the timeout.
I'm not sure where to fix this, but I hope these findings will help.
Hrm, this looks very similar to #4096. Can you both try creating clean virtual environments and installing the newest release of Requests to see if the problem persists?
I've recreated the whole virtualenv from scratch. The problem persist with Request 2.16.5.
It's happening when it's used under google app engine with the adaptor from requests-toolbelt package.
The actual code that breaks is in urllib3 package, as per my previous comment.
However, I believe the reason why it's breaking is related to the hack in the requests/packages.py file
Here is the full call-stack for an example of a breaking HTTPS request:
```self.request('PATCH', url, data=data, *kwargs)
File "/Volumes/Data/Documents/Ride/repos/rideapp/virtualenv/lib/python2.7/site-packages/requests/sessions.py", line 523, in request
resp = self.send(prep, *send_kwargs)
File "/Volumes/Data/Documents/Ride/repos/rideapp/virtualenv/lib/python2.7/site-packages/requests/sessions.py", line 643, in send
r = adapter.send(request, *kwargs)
File "/Volumes/Data/Documents/Ride/repos/rideapp/virtualenv/lib/python2.7/site-packages/requests/adapters.py", line 440, in send
timeout=timeout
File "/Volumes/Data/Documents/Ride/repos/rideapp/virtualenv/lib/python2.7/site-packages/requests_toolbelt/adapters/appengine.py", line 172, in urlopen
*response_kw)
File "/Volumes/Data/Documents/Ride/repos/rideapp/virtualenv/lib/python2.7/site-packages/urllib3/contrib/appengine.py", line 149, in urlopen
validate_certificate=self.validate_certificate,
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch.py", line 293, in fetch
return rpc.get_result()
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
return self.__get_result_hook(self)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch.py", line 413, in _get_fetch_result
rpc.check_success()
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 579, in check_success
self.__rpc.CheckSuccess()
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/apiproxy_rpc.py", line 157, in _WaitImpl
self.request, self.response)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py", line 206, in MakeSyncCall
self._MakeRealSyncCall(service, call, request, response)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py", line 255, in _MakeRealSyncCall
request_pb.set_request(request.Encode())
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 103, in Encode
self.Output(e)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 347, in Output
self.OutputUnchecked(e)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/appengine/api/urlfetch_service_pb.py", line 484, in OutputUnchecked
out.putDouble(self.deadline_)
File "/Users/dana/Downloads/google-cloud-sdk/platform/google_appengine/google/net/proto/ProtocolBuffer.py", line 592, in putDouble
a.fromstring(struct.pack("
@neonihil Do the problems with identity persist though?
@Lukasa Yes it does.
What happens is that in lib/python2.7/site-packages/urllib3/contrib/appengine.py there is: AppEngineManager.urlopen, which has the following block:
response = urlfetch.fetch(
url,
payload=body,
method=method,
headers=headers or {},
allow_truncated=False,
follow_redirects=self.urlfetch_retries and follow_redirects,
deadline=self._get_absolute_timeout(timeout),
validate_certificate=self.validate_certificate,
)
The problem is that self._get_absolute_timeout(timeout) returns a urllib3.utils.Timeout object instead of an int. This is the breaking call.
It returns a Timeout object is because in _get_absolute_timeout(timeout) the following isinstance() call returns False: if isinstance(timeout, Timeout):
The module lib/python2.7/site-packages/urllib3/contrib/appengine.py is importing the Timeout object with the following statement: from ..util.timeout import Timeout.
Interestingly, the imported class is <class 'requests.packages.urllib3.util.timeout.Timeout'>. That is different from what _get_absolute_timeout(timeout) receives as the timeout argument. It's receiving <class 'urllib3.util.timeout.Timeout'>.
It's not a typo, it's really this way. I've confirmed it with the prints in my previous comment.
I appreciate that you confirmed it, but I wanted to try to confirm it again with the clean virtualenv. A very similar problem appeared elsewhere that I cannot reproduce with the current master, and I was wondering if any holdovers from the old installation may be affecting this.
So my new theory is that the grafting done in 2.16.2 only works for anything imported as part of import urllib3. This will not include contrib modules, so they get their namespaces messed up when they eventually are imported.
I've re-created the virtualenv from scratch, starting with rm -rf virtualenv.
Yeah, so I think that @kennethreitz has some work to do to ensure that the contrib modules are also imported and grafted into the namespace, if possible.
This work had been done to support downstream redistributor unvendoring but I don't know what broke that. What @kennethreitz needs to do is go back to an older version of requests/packages/__init__.py to clean this up and see what was being done there. This was a fixed issue for downstream redistributor unvendoring.
@sigmavirus24 our code now is almost identical to the code we were using there, but i'll double check.
The entirety of our old __init__.py was as follows:
from __future__ import absolute_import
import sys
try:
from . import urllib3
except ImportError:
import urllib3
sys.modules['%s.urllib3' % __name__] = urllib3
try:
from . import chardet
except ImportError:
import chardet
sys.modules['%s.chardet' % __name__] = chardet
What we're doing now is identical, save for the __name__ stuff.
It's quite possible that the fallback code never worked either; and we didn't notice because the people who used the contrib modules never used the distribution-unbundled code
I know how to make it work, it's just a matter of identifying all the un-imported namespaces in the urllib3 codebase.
I'll start with contrib.
Here's a report of the monkeypatching not working with Requests v2.14.2 https://github.com/sigmavirus24/requests-toolbelt/issues/184. Related?
@kennethreitz: Have been watching this issue today (thanks for collecting info here).
We have 2.14.2 working fine on our end with requests_toolbelt on GAE Standard. We hit this issue this morning on new CI builds, and pinned to 2.14.2 to solve it (after reading what you found out).
I believe the issue at sigmavirus24/requests-toolbelt#184 is specifically because the user was vendoring in a separate (un-patched) copy of the code in a different import path after the monkey patching took place. So, probably un-related.
@bsmithyman can you try 2.17.3?
@kennethreitz: Can do; will hit it first thing in the morning (EDT) with one of our build servers. Setting a reminder for myself now.
Testing looks pretty solid so far with 2.17.3. Definitely we aren't hitting the same issue as before. We are using the global monkey patching in requests_toolbelt. Confirmed that it's working both with our own code and with vendored libraries that don't know about GAE (e.g., braintree). This is on App Engine Standard.
@bsmithyman excellent news!
Oh, good deal. I'm glad this wasn't my fault for once! :D
You guys are freaking awesome! Thanks for looking at this!
🍰 Closing this
This issue happened again for 2.18. I used 2.17.3 instead per the comment above.
@robincrlee if this has regressed, please open a new issue with more detail than you've already provided.