Repository: twilio/twilio-python

Issue:
[DOCS INCONSISTENCY] rest client parameters have no auth_token
### Issue Summary
Rest client does not take auth_token as an argument, but username and password, even though all over documentation auth_token is used

### Steps to Reproduce
1. import rest client
2. try to initialize client with sid and auth_token
3. client does not take auth_token

### Technical details:
* twilio-python version: 6.46.0
* python version: 3.8.1



Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=twilio_twilio-python&pullRequest=684)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=twilio_twilio-python&pullRequest=684&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=twilio_twilio-python&pullRequest=684&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=twilio_twilio-python&pullRequest=684&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=twilio_twilio-python&pullRequest=684&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=twilio_twilio-python&pullRequest=684) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=twilio_twilio-python&pullRequest=684&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/README.md b/README.md
index bc7f6031f..7edca27d7 100644
--- a/README.md
+++ b/README.md
@@ -58,15 +58,26 @@ Getting started with the Twilio API couldn't be easier. Create a
 
 ### API Credentials
 
-The `Twilio` needs your Twilio credentials. You can either pass these
+The `Twilio` client needs your Twilio credentials. You can either pass these
 directly to the constructor (see the code below) or via environment variables.
 
+Authenticating with Account SID and Auth Token:
 ```python
 from twilio.rest import Client
 
-account = "ACXXXXXXXXXXXXXXXXX"
-token = "YYYYYYYYYYYYYYYYYY"
-client = Client(account, token)
+account_sid = "ACXXXXXXXXXXXXXXXXX"
+auth_token = "YYYYYYYYYYYYYYYYYY"
+client = Client(account_sid, auth_token)
+```
+
+Authenticating with API Key and API Secret:
+```python
+from twilio.rest import Client
+
+api_key = "XXXXXXXXXXXXXXXXX"
+api_secret = "YYYYYYYYYYYYYYYYYY"
+account_sid = "ACXXXXXXXXXXXXXXXXX"
+client = Client(api_key, api_secret, account_sid)
 ```
 
 Alternatively, a `Client` constructor without these parameters will
@@ -111,9 +122,9 @@ This will result in the `hostname` transforming from `api.twilio.com` to `api.sy
 ```python
 from twilio.rest import Client
 
-account = "ACXXXXXXXXXXXXXXXXX"
-token = "YYYYYYYYYYYYYYYYYY"
-client = Client(account, token)
+username = "ACXXXXXXXXXXXXXXXXX"
+password = "YYYYYYYYYYYYYYYYYY"
+client = Client(username, password)
 
 call = client.calls.create(to="9991231234",
                            from_="9991231234",
@@ -126,9 +137,9 @@ print(call.sid)
 ```python
 from twilio.rest import Client
 
-account = "ACXXXXXXXXXXXXXXXXX"
-token = "YYYYYYYYYYYYYYYYYY"
-client = Client(account, token)
+username = "ACXXXXXXXXXXXXXXXXX"
+password = "YYYYYYYYYYYYYYYYYY"
+client = Client(username, password)
 
 message = client.messages.create(to="+12316851234", from_="+15555555555",
                                  body="Hello there!")
@@ -143,10 +154,10 @@ from twilio.http.async_http_client import AsyncTwilioHttpClient
 from twilio.rest import Client
 
 async def main():
-    account = "ACXXXXXXXXXXXXXXXXX"
-    token = "YYYYYYYYYYYYYYYYYY"
+    username = "ACXXXXXXXXXXXXXXXXX"
+    password = "YYYYYYYYYYYYYYYYYY"
     http_client = AsyncTwilioHttpClient()
-    client = Client(account, token, http_client=http_client)
+    client = Client(username, password, http_client=http_client)
 
     message = await client.messages.create_async(to="+12316851234", from_="+15555555555",
                                                  body="Hello there!")
@@ -161,7 +172,7 @@ Log the API request and response data to the console:
 ```python
 import logging
 
-client = Client(account, token)
+client = Client(username, password)
 logging.basicConfig()
 client.http_client.logger.setLevel(logging.INFO)
 ```
@@ -171,7 +182,7 @@ Log the API request and response data to a file:
 ```python
 import logging
 
-client = Client(account, token)
+client = Client(username, password)
 logging.basicConfig(filename='./log.txt')
 client.http_client.logger.setLevel(logging.INFO)
 ```
@@ -182,9 +193,9 @@ client.http_client.logger.setLevel(logging.INFO)
 from twilio.rest import Client
 from twilio.base.exceptions import TwilioRestException
 
-account = "ACXXXXXXXXXXXXXXXXX"
-token = "YYYYYYYYYYYYYYYYYY"
-client = Client(account, token)
+username = "ACXXXXXXXXXXXXXXXXX"
+password = "YYYYYYYYYYYYYYYYYY"
+client = Client(username, password)
 
 try:
   message = client.messages.create(to="+12316851234", from_="+15555555555",
diff --git a/twilio/http/async_http_client.py b/twilio/http/async_http_client.py
index 95ffc32e2..72845ceda 100644
--- a/twilio/http/async_http_client.py
+++ b/twilio/http/async_http_client.py
@@ -1,7 +1,6 @@
-import aiohttp
 import logging
 
-from aiohttp import BasicAuth
+from aiohttp import BasicAuth, ClientSession
 from aiohttp_retry import ExponentialRetry, RetryClient
 from twilio.http import AsyncHttpClient
 from twilio.http.request import Request as TwilioRequest
@@ -39,7 +38,7 @@ class AsyncTwilioHttpClient(AsyncHttpClient):
         self.proxy_url = proxy_url
         self.trace_configs = trace_configs
         self.session = (
-            aiohttp.ClientSession(trace_configs=self.trace_configs)
+            ClientSession(trace_configs=self.trace_configs)
             if pool_connections
             else None
         )
@@ -102,7 +101,7 @@ class AsyncTwilioHttpClient(AsyncHttpClient):
         if self.session:
             session = self.session
         else:
-            session = aiohttp.ClientSession()
+            session = ClientSession()
             temp = True
         self._test_only_last_request = TwilioRequest(**kwargs)
         response = await session.request(**kwargs)
@@ -131,4 +130,5 @@ class AsyncTwilioHttpClient(AsyncHttpClient):
         """
         Async context manager exit
         """
-        await self.session.close()
+        if self.session:
+            await self.session.close()
diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py
index 991fef285..ee42fc2d5 100644
--- a/twilio/http/http_client.py
+++ b/twilio/http/http_client.py
@@ -69,7 +69,9 @@ class TwilioHttpClient(HttpClient):
         :return: An http response
         :rtype: A :class:`Response <twilio.rest.http.response.Response>` object
         """
-        if timeout is not None and timeout <= 0:
+        if timeout is None:
+            timeout = self.timeout
+        elif timeout <= 0:
             raise ValueError(timeout)
 
         kwargs = {
@@ -95,10 +97,12 @@ class TwilioHttpClient(HttpClient):
             prepped_request.url, self.proxy, None, None, None
         )
 
-        settings["allow_redirects"] = allow_redirects
-        settings["timeout"] = timeout if timeout is not None else self.timeout
-
-        response = session.send(prepped_request, **settings)
+        response = session.send(
+            prepped_request,
+            allow_redirects=allow_redirects,
+            timeout=timeout,
+            **settings
+        )
 
         self.log_response(response.status_code, response)
 
diff --git a/twilio/http/request.py b/twilio/http/request.py
index 5ec2ed3c2..2380b0241 100644
--- a/twilio/http/request.py
+++ b/twilio/http/request.py
@@ -1,17 +1,30 @@
+from enum import Enum
+from typing import Dict, Union
 from urllib.parse import urlencode
 
 
+class Match(Enum):
+    ANY = "*"
+
+
 class Request(object):
     """
     An HTTP request.
     """
 
-    ANY = "*"
-
     def __init__(
-        self, method=ANY, url=ANY, auth=ANY, params=ANY, data=ANY, headers=ANY, **kwargs
+        self,
+        method: Union[str, Match] = Match.ANY,
+        url: Union[str, Match] = Match.ANY,
+        auth: Union[tuple, Match] = Match.ANY,
+        params: Union[Dict, Match] = Match.ANY,
+        data: Union[Dict, Match] = Match.ANY,
+        headers: Union[Dict, Match] = Match.ANY,
+        **kwargs
     ):
-        self.method = method.upper()
+        self.method = method
+        if method and method != Match.ANY:
+            self.method = method.upper()
         self.url = url
         self.auth = auth
         self.params = params
@@ -20,7 +33,7 @@ class Request(object):
 
     @classmethod
     def attribute_equal(cls, lhs, rhs):
-        if lhs == cls.ANY or rhs == cls.ANY:
+        if lhs == Match.ANY or rhs == Match.ANY:
             # ANY matches everything
             return True
 
@@ -44,15 +57,15 @@ class Request(object):
 
     def __str__(self):
         auth = ""
-        if self.auth and self.auth != self.ANY:
+        if self.auth and self.auth != Match.ANY:
             auth = "{} ".format(self.auth)
 
         params = ""
-        if self.params and self.params != self.ANY:
+        if self.params and self.params != Match.ANY:
             params = "?{}".format(urlencode(self.params, doseq=True))
 
         data = ""
-        if self.data and self.data != self.ANY:
+        if self.data and self.data != Match.ANY:
             if self.method == "GET":
                 data = "\n -G"
             data += "\n{}".format(
@@ -60,7 +73,7 @@ class Request(object):
             )
 
         headers = ""
-        if self.headers and self.headers != self.ANY:
+        if self.headers and self.headers != Match.ANY:
             headers = "\n{}".format(
                 "\n".join(' -H "{}: {}"'.format(k, v) for k, v in self.headers.items())
             )
diff --git a/twilio/http/validation_client.py b/twilio/http/validation_client.py
index 4e00064a5..c8e211ddf 100644
--- a/twilio/http/validation_client.py
+++ b/twilio/http/validation_client.py
@@ -121,7 +121,7 @@ class ValidationClient(HttpClient):
     def _get_host(self, request):
         """Pull the Host out of the request"""
         parsed = urlparse(request.url)
-        return parsed.netloc
+        return str(parsed.netloc)
 
     def validate_ssl_certificate(self, client):
         """
diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py
index 727e0aa9b..a50b81217 100644
--- a/twilio/rest/__init__.py
+++ b/twilio/rest/__init__.py
@@ -28,9 +28,9 @@ class Client(ClientBase):
         """
         Initializes the Twilio Client
 
-        :param str username: Username to authenticate with
-        :param str password: Password to authenticate with
-        :param str account_sid: Account SID, defaults to Username
+        :param str username: Username to authenticate with, either account_sid or api_key
+        :param str password: Password to authenticate with, auth_token (if using account_sid) or api_secret (if using api_key)
+        :param str account_sid: Account SID, required if using api_key to authenticate.
         :param str region: Twilio Region to make requests to, defaults to 'us1' if an edge is provided
         :param HttpClient http_client: HttpClient, defaults to TwilioHttpClient
         :param dict environment: Environment to look for auth details, defaults to os.environ


Test patch:
diff --git a/tests/unit/http/test_async_http_client.py b/tests/unit/http/test_async_http_client.py
index dd414b5a0..df4360684 100644
--- a/tests/unit/http/test_async_http_client.py
+++ b/tests/unit/http/test_async_http_client.py
@@ -24,9 +24,7 @@ class TestAsyncHttpClientRequest(aiounittest.AsyncTestCase):
         self.session_mock = AsyncMock(wraps=ClientSession)
         self.session_mock.request.return_value = MockResponse("test", 200)
 
-        self.session_patcher = patch(
-            "twilio.http.async_http_client.aiohttp.ClientSession"
-        )
+        self.session_patcher = patch("twilio.http.async_http_client.ClientSession")
         session_constructor_mock = self.session_patcher.start()
         session_constructor_mock.return_value = self.session_mock
 
@@ -69,9 +67,7 @@ class TestAsyncHttpClientRetries(aiounittest.AsyncTestCase):
             MockResponse("Success", 200),
         ]
 
-        self.session_patcher = patch(
-            "twilio.http.async_http_client.aiohttp.ClientSession"
-        )
+        self.session_patcher = patch("twilio.http.async_http_client.ClientSession")
         session_constructor_mock = self.session_patcher.start()
         session_constructor_mock.return_value = self.session_mock
 
@@ -97,9 +93,7 @@ class TestAsyncHttpClientRetries(aiounittest.AsyncTestCase):
 
 class TestAsyncHttpClientSession(aiounittest.AsyncTestCase):
     def setUp(self):
-        self.session_patcher = patch(
-            "twilio.http.async_http_client.aiohttp.ClientSession"
-        )
+        self.session_patcher = patch("twilio.http.async_http_client.ClientSession")
         self.session_constructor_mock = self.session_patcher.start()
 
     def tearDown(self):
diff --git a/tests/unit/http/test_http_client.py b/tests/unit/http/test_http_client.py
index dbd4b95fa..2730c2548 100644
--- a/tests/unit/http/test_http_client.py
+++ b/tests/unit/http/test_http_client.py
@@ -46,7 +46,7 @@ class TestHttpClientRequest(unittest.TestCase):
         self.request_mock.headers = {"Host": "other.twilio.com"}
 
         response = self.client.request(
-            "doesnt matter", "doesnt matter", None, None, None, None, 30, None
+            "doesnt matter", "doesnt matter", None, None, None, None, 30
         )
 
         self.assertEqual("other.twilio.com", self.request_mock.headers["Host"])
@@ -59,7 +59,7 @@ class TestHttpClientRequest(unittest.TestCase):
 
         try:
             self.client.request(
-                "doesnt matter", "doesnt matter", None, None, None, None, 0, None
+                "doesnt matter", "doesnt matter", None, None, None, None, 0
             )
         except Exception as e:
             self.assertEqual(ValueError, type(e))
@@ -90,7 +90,7 @@ class TestHttpClientRequest(unittest.TestCase):
         self.client.timeout = 30
 
         response = self.client.request(
-            "doesnt matter", "doesnt matter", None, None, None, None, 15, None
+            "doesnt matter", "doesnt matter", None, None, None, None, 15
         )
 
         self.assertEqual("other.twilio.com", self.request_mock.headers["Host"])
@@ -138,10 +138,12 @@ class TestHttpClientRequest(unittest.TestCase):
         self.assertEqual(["a", "b"], self.client._test_only_last_request.auth)
 
         self.assertIsNotNone(self.client._test_only_last_response)
-        self.assertEqual(200, self.client._test_only_last_response.status_code)
-        self.assertEqual(
-            "testing-unicode: Ω≈ç√, 💩", self.client._test_only_last_response.text
-        )
+
+        if self.client._test_only_last_response is not None:
+            self.assertEqual(200, self.client._test_only_last_response.status_code)
+            self.assertEqual(
+                "testing-unicode: Ω≈ç√, 💩", self.client._test_only_last_response.text
+            )
 
     def test_last_response_empty_on_error(self):
         self.session_mock.send.side_effect = Exception("voltron")
diff --git a/tests/unit/http/test_validation_client.py b/tests/unit/http/test_validation_client.py
index 60840d5e6..5fdd4cb9f 100644
--- a/tests/unit/http/test_validation_client.py
+++ b/tests/unit/http/test_validation_client.py
@@ -30,7 +30,7 @@ class TestValidationClientHelpers(unittest.TestCase):
         self.assertEqual("", validation_payload.body)
 
     def test_build_validation_payload_query_string_parsed(self):
-        self.request.url = self.request.url + "?QueryParam=1&Other=true"
+        self.request.url = (self.request.url or "") + "?QueryParam=1&Other=true"
 
         validation_payload = self.client._build_validation_payload(self.request)
 
@@ -41,7 +41,7 @@ class TestValidationClientHelpers(unittest.TestCase):
         self.assertEqual("", validation_payload.body)
 
     def test_build_validation_payload_body_parsed(self):
-        self.request.body = "foobar"
+        setattr(self.request, "body", "foobar")
 
         validation_payload = self.client._build_validation_payload(self.request)
 
@@ -52,10 +52,10 @@ class TestValidationClientHelpers(unittest.TestCase):
         self.assertEqual("foobar", validation_payload.body)
 
     def test_build_validation_payload_complex(self):
-        self.request.body = "foobar"
+        setattr(self.request, "body", "foobar")
         self.request.url = (
-            self.request.url + "?QueryParam=Value&OtherQueryParam=OtherValue"
-        )
+            self.request.url or ""
+        ) + "?QueryParam=Value&OtherQueryParam=OtherValue"
 
         validation_payload = self.client._build_validation_payload(self.request)