Repository: getmoto/moto

Issue:
Should Moto mock external requests?
While moto is active, should requests to other sites (ex `http://www.google.com`) still work?

I'm leaning toward yes. At the same time, I think it is important that any amazon service that is not implemented yet throw an error (see #1254).

I believe this means we should add a passthrough for `*` , but add a fake error responses for `*amazonaws.com*`

Thoughts?


Discussion:
personally I'd like to see the redirection work through proxying, without any patching to the underlying http client, perhaps requiring users to explicitly set an AWS client's proxy, ex: `boto3.resource('s3', config=Config(proxies={'https': 'foo.bar:3128'}))`
+1 for passthroughs to external sites and throwing an error for unsupported services.  I'm hearing from others that requests to AWS interleaved with calls to other services is common.

Additionally, clean stress-free unpatching of moto is still important IMO even if we had default passthroughs.  To get the best test coverage, I use both moto and localstack, the latter of which is hitting a docker container on localhost.  I don't generally need to combine the use of these two technologies in a single test case, but within a single run of the suite both will be used. If moto is not unpatched properly, it breaks tests based on localstack and also anything else that happens to use `requests`.  So far, I've not yet been able to get unpatching to work, even with the current release.
@killthrush thanks for the feedback.

Just to confirm, the unpatching is still failing on `1.3.2`? If so, any chance you could add some example code to https://github.com/spulec/moto/issues/1026. I'd really like to get that fixed.
Thanks @spulec - I'm working on a repo for this.  I've reproduced the problem with `1.3.2`, but only if I run tests a certain way.  I think there may be something more subtle going on here.   Will keep you posted.
Alright, I think I've narrowed this down to something more specific.  v1.3.2 fixes the mock problem when running pytest from the command line or when running pytest directly using `python -m pytest test_dir`.  However PyCharm's pytest runner still fails for some reason on this particular case.  Would it be more helpful if I just wrote up a bug for this?  I don't want to detract from your original question ^^^
@killthrush yes please!
@spulec - never mind, the PyCharm issue was my mistake.  I was writing up a bug report and I noticed that my config was pointing to the wrong virtualenv =/

So #1026 is indeed fixed and there's no bug here.  Thanks!  This was a really good fix 🎉 
@killthrush thanks for following up!
@spulec yeah, we should stop mocking the full requests lib, its just more trouble than its worth. The biggest complain i get from my coworkers about moto is this.
@thehesiod not against this, does that mean that moto will be the mock server? Will this just become another https://github.com/localstack/localstack ??
@kingbuzzman can you clarify a bit: are the complaints about mocking urls beyond AWS or mucking with the requests module in general?
@spulec the issue is mocking urls beyond what is needed to make aws work.
@kingbuzzman got it; very helpful, thanks.
@spulec hi! i'm loving moto, it has saved me countless hours.

A specific integration case i'm dealing with right now:

Only during initial integration test development, it is helpful to allow a mock SQS send/receive for one unit but still allow another unit to have outbound requests to other URLs.

Having to disable the decorator just to get both units to work means having to hit my staging SQS queue until I can mock the second unit.

This is a small headache of using moto, I haven't looked deeper to see how to fix however some workarounds are floating around the issue list. (like this one: https://github.com/spulec/moto/issues/1026#issuecomment-380092775)
Any progress on this?
@kingbuzzman I haven't been following things much, how do moto and localstack compare? They seem very similar?  This is how I currently use moto with process isolation between services and w/o mocking external requests: http://hesiod.blogspot.com/2017/10/mocking-aws-lambdas.html  My idea for proxy support is that you'd have one endpoint instead that then routes the AWS calls to each individual aws service endpoint, instead of having an endpoint_url per service.
@thehesiod Well like everything atlassian.. localstack promises a lot and delivers on very little. As of right now, localstack and moto are close, but there are clear differences between the two. The second you start doing more than mocking the boto calls and start having a feature full/partial of what those boto calls do -- you're going to be in localstack territory.

In the spirit of honesty. I've used localstack very little/at all -- to the point that anytime i have an issue and i say to myself, "neat! i can use localstack for this" and always follow it with a quick look at their website to say: "#$%^ they dont support my feature!". Needless to say, take everything i say with a grain of salt.

@spulec whats the deal with the mocking of external requests other than moto?
Hello all,

From other threads it seems that this issue would be solved by version 1.0.0.  I just installed 1.3.5 today but it seems that it's still mocking all requests.  I open connections to other sites while using DynamoDB... is there a workaround for this, or should I upgrade any other package that moto depends on?

Thank you!
@csolamx I don't think this is "solved" yet, see my post above for how I accomplish this
This will potentially be solved with https://github.com/spulec/moto/pull/1847

Resolving patch:
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -388,10 +388,16 @@ def update_integration_mocks(self, stage_name):
         stage_url_upper = STAGE_URL.format(api_id=self.id.upper(),
             region_name=self.region_name, stage_name=stage_name)
 
-        responses.add_callback(responses.GET, stage_url_lower,
-                               callback=self.resource_callback)
-        responses.add_callback(responses.GET, stage_url_upper,
-                               callback=self.resource_callback)
+        for url in [stage_url_lower, stage_url_upper]:
+            responses._default_mock._matches.insert(0,
+                responses.CallbackResponse(
+                    url=url,
+                    method=responses.GET,
+                    callback=self.resource_callback,
+                    content_type="text/plain",
+                    match_querystring=False,
+                )
+            )
 
     def create_stage(self, name, deployment_id, variables=None, description='', cacheClusterEnabled=None, cacheClusterSize=None):
         if variables is None:
diff --git a/moto/core/models.py b/moto/core/models.py
--- a/moto/core/models.py
+++ b/moto/core/models.py
@@ -193,53 +193,8 @@ def _url_matches(self, url, other, match_querystring=False):
 
 botocore_mock = responses.RequestsMock(assert_all_requests_are_fired=False, target='botocore.vendored.requests.adapters.HTTPAdapter.send')
 responses_mock = responses._default_mock
-
-
-class ResponsesMockAWS(BaseMockAWS):
-    def reset(self):
-        botocore_mock.reset()
-        responses_mock.reset()
-
-    def enable_patching(self):
-        if not hasattr(botocore_mock, '_patcher') or not hasattr(botocore_mock._patcher, 'target'):
-            # Check for unactivated patcher
-            botocore_mock.start()
-
-        if not hasattr(responses_mock, '_patcher') or not hasattr(responses_mock._patcher, 'target'):
-            responses_mock.start()
-
-        for method in RESPONSES_METHODS:
-            for backend in self.backends_for_urls.values():
-                for key, value in backend.urls.items():
-                    responses_mock.add(
-                        CallbackResponse(
-                            method=method,
-                            url=re.compile(key),
-                            callback=convert_flask_to_responses_response(value),
-                            stream=True,
-                            match_querystring=False,
-                        )
-                    )
-                    botocore_mock.add(
-                        CallbackResponse(
-                            method=method,
-                            url=re.compile(key),
-                            callback=convert_flask_to_responses_response(value),
-                            stream=True,
-                            match_querystring=False,
-                        )
-                    )
-
-    def disable_patching(self):
-        try:
-            botocore_mock.stop()
-        except RuntimeError:
-            pass
-
-        try:
-            responses_mock.stop()
-        except RuntimeError:
-            pass
+# Add passthrough to allow any other requests to work
+responses_mock.add_passthru("http")
 
 
 BOTOCORE_HTTP_METHODS = [
@@ -306,6 +261,14 @@ def __call__(self, event_name, request, **kwargs):
 BUILTIN_HANDLERS.append(('before-send', botocore_stubber))
 
 
+def not_implemented_callback(request):
+    status = 400
+    headers = {}
+    response = "The method is not implemented"
+
+    return status, headers, response
+
+
 class BotocoreEventMockAWS(BaseMockAWS):
     def reset(self):
         botocore_stubber.reset()
@@ -335,6 +298,24 @@ def enable_patching(self):
                             match_querystring=False,
                         )
                     )
+            responses_mock.add(
+                CallbackResponse(
+                    method=method,
+                    url=re.compile("https?://.+.amazonaws.com/.*"),
+                    callback=not_implemented_callback,
+                    stream=True,
+                    match_querystring=False,
+                )
+            )
+            botocore_mock.add(
+                CallbackResponse(
+                    method=method,
+                    url=re.compile("https?://.+.amazonaws.com/.*"),
+                    callback=not_implemented_callback,
+                    stream=True,
+                    match_querystring=False,
+                )
+            )
 
     def disable_patching(self):
         botocore_stubber.enabled = False


Test patch:
diff --git a/tests/test_core/test_request_mocking.py b/tests/test_core/test_request_mocking.py
new file mode 100644
--- /dev/null
+++ b/tests/test_core/test_request_mocking.py
@@ -0,0 +1,22 @@
+import requests
+import sure  # noqa
+
+import boto3
+from moto import mock_sqs, settings
+
+
+@mock_sqs
+def test_passthrough_requests():
+    conn = boto3.client("sqs", region_name='us-west-1')
+    conn.create_queue(QueueName="queue1")
+
+    res = requests.get("https://httpbin.org/ip")
+    assert res.status_code == 200
+
+
+if not settings.TEST_SERVER_MODE:
+    @mock_sqs
+    def test_requests_to_amazon_subdomains_dont_work():
+        res = requests.get("https://fakeservice.amazonaws.com/foo/bar")
+        assert res.content == b"The method is not implemented"
+        assert res.status_code == 400