diff --git a/README.md b/README.md
index 95cdc6f1c1..0cbc53231b 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ openai api completions.create -e ada -p "Hello world"
 
 ## Requirements
 
--   Python 3.4+
+-   Python 3.6+
 
 In general we want to support the versions of Python that our
 customers are using, so if you run into issues with any version
diff --git a/openai/__init__.py b/openai/__init__.py
index 694db8ba3b..38fa9a51d7 100644
--- a/openai/__init__.py
+++ b/openai/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
 import os
 
 # OpenAI Python bindings.
@@ -27,6 +25,15 @@
 log = None
 
 # API resources
-from openai.api_resources import *  # noqa
+from openai.api_resources import (  # noqa: E402,F401
+    Answer,
+    Classification,
+    Completion,
+    Engine,
+    ErrorObject,
+    File,
+    FineTune,
+    Snapshot,
+)
 
-from openai.error import OpenAIError, APIError, InvalidRequestError
+from openai.error import OpenAIError, APIError, InvalidRequestError  # noqa: E402,F401
diff --git a/openai/api_requestor.py b/openai/api_requestor.py
index 186bdd284a..5202a7d7d2 100644
--- a/openai/api_requestor.py
+++ b/openai/api_requestor.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
 import calendar
 import datetime
 import json
@@ -7,19 +5,19 @@
 import time
 import uuid
 import warnings
-import gzip
 from io import BytesIO
 from collections import OrderedDict
+from urllib.parse import urlencode, urlsplit, urlunsplit
 
 import openai
-from openai import error, http_client, version, util, six
+from openai import error, http_client, version, util
 from openai.multipart_data_generator import MultipartDataGenerator
-from openai.six.moves.urllib.parse import urlencode, urlsplit, urlunsplit
 from openai.openai_response import OpenAIResponse
 from openai.upload_progress import BufferReader
 
 
-def _encode_datetime(dttime):
+def _encode_datetime(dttime) -> int:
+    utc_timestamp: float
     if dttime.tzinfo and dttime.tzinfo.utcoffset(dttime) is not None:
         utc_timestamp = calendar.timegm(dttime.utctimetuple())
     else:
@@ -30,14 +28,13 @@ def _encode_datetime(dttime):
 
 def _encode_nested_dict(key, data, fmt="%s[%s]"):
     d = OrderedDict()
-    for subkey, subvalue in six.iteritems(data):
+    for subkey, subvalue in data.items():
         d[fmt % (key, subkey)] = subvalue
     return d
 
 
 def _api_encode(data):
-    for key, value in six.iteritems(data):
-        key = util.utf8(key)
+    for key, value in data.items():
         if value is None:
             continue
         elif hasattr(value, "openai_id"):
@@ -49,7 +46,7 @@ def _api_encode(data):
                     for k, v in _api_encode(subdict):
                         yield (k, v)
                 else:
-                    yield ("%s[%d]" % (key, i), util.utf8(sv))
+                    yield ("%s[%d]" % (key, i), sv)
         elif isinstance(value, dict):
             subdict = _encode_nested_dict(key, value)
             for subkey, subvalue in _api_encode(subdict):
@@ -57,7 +54,7 @@ def _api_encode(data):
         elif isinstance(value, datetime.datetime):
             yield (key, _encode_datetime(value))
         else:
-            yield (key, util.utf8(value))
+            yield (key, value)
 
 
 def _build_api_url(url, query):
@@ -81,7 +78,7 @@ def parse_stream(rbody):
             yield line
 
 
-class APIRequestor(object):
+class APIRequestor:
     def __init__(
         self, key=None, client=None, api_base=None, api_version=None, organization=None
     ):
@@ -205,20 +202,13 @@ def request_headers(self, api_key, method, extra):
 
         ua = {
             "bindings_version": version.VERSION,
+            "httplib": self._client.name,
             "lang": "python",
+            "lang_version": platform.python_version(),
+            "platform": platform.platform(),
             "publisher": "openai",
-            "httplib": self._client.name,
+            "uname": " ".join(platform.uname()),
         }
-        for attr, func in [
-            ["lang_version", platform.python_version],
-            ["platform", platform.platform],
-            ["uname", lambda: " ".join(platform.uname())],
-        ]:
-            try:
-                val = func()
-            except Exception as e:
-                val = "!! %s" % (e,)
-            ua[attr] = val
         if openai.app_info:
             ua["application"] = openai.app_info
 
@@ -257,7 +247,7 @@ def request_raw(
 
         if my_api_key is None:
             raise error.AuthenticationError(
-                "No API key provided. (HINT: set your API key using in code using "
+                "No API key provided. (HINT: set your API key in code using "
                 '"openai.api_key = <API-KEY>", or you can set the environment variable OPENAI_API_KEY=<API-KEY>). You can generate API keys '
                 "in the OpenAI web interface. See https://onboard.openai.com "
                 "for details, or email support@openai.com if you have any "
@@ -320,7 +310,7 @@ def request_raw(
 
         headers = self.request_headers(my_api_key, method, headers)
         if supplied_headers is not None:
-            for key, value in six.iteritems(supplied_headers):
+            for key, value in supplied_headers.items():
                 headers[key] = value
 
         util.log_info("Request to OpenAI API", method=method, path=abs_url)
diff --git a/openai/api_resources/__init__.py b/openai/api_resources/__init__.py
index 3cc8cc3f9f..810bfd1725 100644
--- a/openai/api_resources/__init__.py
+++ b/openai/api_resources/__init__.py
@@ -1,8 +1,8 @@
-from openai.api_resources.completion import Completion
-from openai.api_resources.engine import Engine
-from openai.api_resources.error_object import ErrorObject
-from openai.api_resources.file import File
-from openai.api_resources.answer import Answer
-from openai.api_resources.classification import Classification
-from openai.api_resources.snapshot import Snapshot
-from openai.api_resources.fine_tune import FineTune
+from openai.api_resources.completion import Completion  # noqa: F401
+from openai.api_resources.engine import Engine  # noqa: F401
+from openai.api_resources.error_object import ErrorObject  # noqa: F401
+from openai.api_resources.file import File  # noqa: F401
+from openai.api_resources.answer import Answer  # noqa: F401
+from openai.api_resources.classification import Classification  # noqa: F401
+from openai.api_resources.snapshot import Snapshot  # noqa: F401
+from openai.api_resources.fine_tune import FineTune  # noqa: F401
diff --git a/openai/api_resources/abstract/__init__.py b/openai/api_resources/abstract/__init__.py
index 69cce2dc33..8b42e409b5 100644
--- a/openai/api_resources/abstract/__init__.py
+++ b/openai/api_resources/abstract/__init__.py
@@ -1,28 +1,12 @@
-from __future__ import absolute_import, division, print_function
-
 # flake8: noqa
 
 from openai.api_resources.abstract.api_resource import APIResource
-from openai.api_resources.abstract.singleton_api_resource import (
-    SingletonAPIResource,
-)
-
-from openai.api_resources.abstract.createable_api_resource import (
-    CreateableAPIResource,
-)
-from openai.api_resources.abstract.updateable_api_resource import (
-    UpdateableAPIResource,
-)
-from openai.api_resources.abstract.deletable_api_resource import (
-    DeletableAPIResource,
-)
-from openai.api_resources.abstract.listable_api_resource import (
-    ListableAPIResource,
-)
-from openai.api_resources.abstract.verify_mixin import VerifyMixin
-
+from openai.api_resources.abstract.singleton_api_resource import SingletonAPIResource
+from openai.api_resources.abstract.createable_api_resource import CreateableAPIResource
+from openai.api_resources.abstract.updateable_api_resource import UpdateableAPIResource
+from openai.api_resources.abstract.deletable_api_resource import DeletableAPIResource
+from openai.api_resources.abstract.listable_api_resource import ListableAPIResource
 from openai.api_resources.abstract.custom_method import custom_method
-
 from openai.api_resources.abstract.nested_resource_class_methods import (
     nested_resource_class_methods,
 )
diff --git a/openai/api_resources/abstract/api_resource.py b/openai/api_resources/abstract/api_resource.py
index f88021f092..289363370c 100644
--- a/openai/api_resources/abstract/api_resource.py
+++ b/openai/api_resources/abstract/api_resource.py
@@ -1,8 +1,7 @@
-from __future__ import absolute_import, division, print_function
+from urllib.parse import quote_plus
 
-from openai import api_requestor, error, six, util
+from openai import api_requestor, error, util
 from openai.openai_object import OpenAIObject
-from openai.six.moves.urllib.parse import quote_plus
 
 
 class APIResource(OpenAIObject):
@@ -28,13 +27,13 @@ def class_url(cls):
             )
         # Namespaces are separated in object names with periods (.) and in URLs
         # with forward slashes (/), so replace the former with the latter.
-        base = cls.OBJECT_NAME.replace(".", "/")
+        base = cls.OBJECT_NAME.replace(".", "/")  # type: ignore
         return "/%s/%ss" % (cls.api_prefix, base)
 
     def instance_url(self):
         id = self.get("id")
 
-        if not isinstance(id, six.string_types):
+        if not isinstance(id, str):
             raise error.InvalidRequestError(
                 "Could not determine which URL to request: %s instance "
                 "has invalid ID: %r, %s. ID should be of type `str` (or"
@@ -42,7 +41,6 @@ def instance_url(self):
                 "id",
             )
 
-        id = util.utf8(id)
         base = self.class_url()
         extn = quote_plus(id)
         return "%s/%s" % (base, extn)
@@ -60,7 +58,7 @@ def _static_request(
         request_id=None,
         api_version=None,
         organization=None,
-        **params
+        **params,
     ):
         requestor = api_requestor.APIRequestor(
             api_key,
diff --git a/openai/api_resources/abstract/createable_api_resource.py b/openai/api_resources/abstract/createable_api_resource.py
index d494f78474..231e4e9ca7 100644
--- a/openai/api_resources/abstract/createable_api_resource.py
+++ b/openai/api_resources/abstract/createable_api_resource.py
@@ -16,7 +16,7 @@ def create(
         request_id=None,
         api_version=None,
         organization=None,
-        **params
+        **params,
     ):
         requestor = api_requestor.APIRequestor(
             api_key,
diff --git a/openai/api_resources/abstract/custom_method.py b/openai/api_resources/abstract/custom_method.py
index b46cb31294..3c3eb8d3ce 100644
--- a/openai/api_resources/abstract/custom_method.py
+++ b/openai/api_resources/abstract/custom_method.py
@@ -1,7 +1,6 @@
-from __future__ import absolute_import, division, print_function
+from urllib.parse import quote_plus
 
 from openai import util
-from openai.six.moves.urllib.parse import quote_plus
 
 
 def custom_method(name, http_verb, http_path=None):
@@ -17,7 +16,7 @@ def wrapper(cls):
         def custom_method_request(cls, sid, **params):
             url = "%s/%s/%s" % (
                 cls.class_url(),
-                quote_plus(util.utf8(sid)),
+                quote_plus(sid),
                 http_path,
             )
             return cls._static_request(http_verb, url, **params)
@@ -33,9 +32,7 @@ def custom_method_request(cls, sid, **params):
             # that the new class method is called when the original method is
             # called as a class method.
             setattr(cls, "_cls_" + name, classmethod(custom_method_request))
-            instance_method = util.class_method_variant("_cls_" + name)(
-                existing_method
-            )
+            instance_method = util.class_method_variant("_cls_" + name)(existing_method)
             setattr(cls, name, instance_method)
 
         return cls
diff --git a/openai/api_resources/abstract/deletable_api_resource.py b/openai/api_resources/abstract/deletable_api_resource.py
index 852d14f587..4bebe3ecb5 100644
--- a/openai/api_resources/abstract/deletable_api_resource.py
+++ b/openai/api_resources/abstract/deletable_api_resource.py
@@ -1,14 +1,13 @@
-from __future__ import absolute_import, division, print_function
+from urllib.parse import quote_plus
 
 from openai import util
 from openai.api_resources.abstract.api_resource import APIResource
-from openai.six.moves.urllib.parse import quote_plus
 
 
 class DeletableAPIResource(APIResource):
     @classmethod
     def _cls_delete(cls, sid, **params):
-        url = "%s/%s" % (cls.class_url(), quote_plus(util.utf8(sid)))
+        url = "%s/%s" % (cls.class_url(), quote_plus(sid))
         return cls._static_request("delete", url, **params)
 
     @util.class_method_variant("_cls_delete")
diff --git a/openai/api_resources/abstract/engine_api_resource.py b/openai/api_resources/abstract/engine_api_resource.py
index 54670a6219..bec3c12023 100644
--- a/openai/api_resources/abstract/engine_api_resource.py
+++ b/openai/api_resources/abstract/engine_api_resource.py
@@ -1,8 +1,9 @@
 import time
+from typing import Optional
+from urllib.parse import quote_plus
 
-from openai import api_requestor, error, six, util
+from openai import api_requestor, error, util
 from openai.api_resources.abstract.api_resource import APIResource
-from openai.six.moves.urllib.parse import quote_plus
 
 MAX_TIMEOUT = 20
 
@@ -11,56 +12,20 @@ class EngineAPIResource(APIResource):
     engine_required = True
     plain_old_data = False
 
-    def __init__(self, *args, **kwargs):
-        engine = kwargs.pop("engine", None)
-        super().__init__(*args, engine=engine, **kwargs)
+    def __init__(self, engine: Optional[str] = None, **kwargs):
+        super().__init__(engine=engine, **kwargs)
 
     @classmethod
-    def class_url(cls, engine=None):
+    def class_url(cls, engine: Optional[str] = None):
         # Namespaces are separated in object names with periods (.) and in URLs
         # with forward slashes (/), so replace the former with the latter.
-        base = cls.OBJECT_NAME.replace(".", "/")
+        base = cls.OBJECT_NAME.replace(".", "/")  # type: ignore
         if engine is None:
             return "/%s/%ss" % (cls.api_prefix, base)
 
-        engine = util.utf8(engine)
         extn = quote_plus(engine)
         return "/%s/engines/%s/%ss" % (cls.api_prefix, extn, base)
 
-    @classmethod
-    def retrieve(cls, id, api_key=None, request_id=None, **params):
-        engine = params.pop("engine", None)
-        instance = cls(id, api_key, engine=engine, **params)
-        instance.refresh(request_id=request_id)
-        return instance
-
-    @classmethod
-    def update(
-        cls,
-        api_key=None,
-        api_base=None,
-        idempotency_key=None,
-        request_id=None,
-        api_version=None,
-        organization=None,
-        **params,
-    ):
-        # TODO max
-        engine_id = params.get("id")
-        replicas = params.get("replicas")
-
-        engine = EngineAPIResource(id=id)
-
-        requestor = api_requestor.APIRequestor(
-            api_key,
-            api_base=api_base,
-            api_version=api_version,
-            organization=organization,
-        )
-        url = cls.class_url(engine)
-        headers = util.populate_headers(idempotency_key, request_id)
-        response, _, api_key = requestor.request("post", url, params, headers)
-
     @classmethod
     def create(
         cls,
@@ -138,7 +103,7 @@ def create(
     def instance_url(self):
         id = self.get("id")
 
-        if not isinstance(id, six.string_types):
+        if not isinstance(id, str):
             raise error.InvalidRequestError(
                 "Could not determine which URL to request: %s instance "
                 "has invalid ID: %r, %s. ID should be of type `str` (or"
@@ -146,7 +111,6 @@ def instance_url(self):
                 "id",
             )
 
-        id = util.utf8(id)
         base = self.class_url(self.engine)
         extn = quote_plus(id)
         url = "%s/%s" % (base, extn)
@@ -158,7 +122,6 @@ def instance_url(self):
         return url
 
     def wait(self, timeout=None):
-        engine = self.engine
         start = time.time()
         while self.status != "complete":
             self.timeout = (
diff --git a/openai/api_resources/abstract/listable_api_resource.py b/openai/api_resources/abstract/listable_api_resource.py
index 3654998bfc..dfdd7a2d25 100644
--- a/openai/api_resources/abstract/listable_api_resource.py
+++ b/openai/api_resources/abstract/listable_api_resource.py
@@ -17,7 +17,7 @@ def list(
         api_version=None,
         organization=None,
         api_base=None,
-        **params
+        **params,
     ):
         headers = util.populate_headers(request_id=request_id)
         requestor = api_requestor.APIRequestor(
diff --git a/openai/api_resources/abstract/nested_resource_class_methods.py b/openai/api_resources/abstract/nested_resource_class_methods.py
index 40de694555..7655a996e6 100644
--- a/openai/api_resources/abstract/nested_resource_class_methods.py
+++ b/openai/api_resources/abstract/nested_resource_class_methods.py
@@ -1,7 +1,6 @@
-from __future__ import absolute_import, division, print_function
+from urllib.parse import quote_plus
 
 from openai import api_requestor, util
-from openai.six.moves.urllib.parse import quote_plus
 
 
 def nested_resource_class_methods(
@@ -33,7 +32,7 @@ def nested_resource_request(
             request_id=None,
             api_version=None,
             organization=None,
-            **params
+            **params,
         ):
             requestor = api_requestor.APIRequestor(
                 api_key, api_version=api_version, organization=organization
diff --git a/openai/api_resources/abstract/singleton_api_resource.py b/openai/api_resources/abstract/singleton_api_resource.py
index 56ffb602b3..0385c7066c 100644
--- a/openai/api_resources/abstract/singleton_api_resource.py
+++ b/openai/api_resources/abstract/singleton_api_resource.py
@@ -17,7 +17,7 @@ def class_url(cls):
             )
         # Namespaces are separated in object names with periods (.) and in URLs
         # with forward slashes (/), so replace the former with the latter.
-        base = cls.OBJECT_NAME.replace(".", "/")
+        base = cls.OBJECT_NAME.replace(".", "/")  # type: ignore
         return "/v1/%s" % (base,)
 
     def instance_url(self):
diff --git a/openai/api_resources/abstract/updateable_api_resource.py b/openai/api_resources/abstract/updateable_api_resource.py
index c3208812a1..9d2050f10a 100644
--- a/openai/api_resources/abstract/updateable_api_resource.py
+++ b/openai/api_resources/abstract/updateable_api_resource.py
@@ -1,14 +1,13 @@
-from __future__ import absolute_import, division, print_function
+from urllib.parse import quote_plus
 
 from openai import util
 from openai.api_resources.abstract.api_resource import APIResource
-from openai.six.moves.urllib.parse import quote_plus
 
 
 class UpdateableAPIResource(APIResource):
     @classmethod
     def modify(cls, sid, **params):
-        url = "%s/%s" % (cls.class_url(), quote_plus(util.utf8(sid)))
+        url = "%s/%s" % (cls.class_url(), quote_plus(sid))
         return cls._static_request("post", url, **params)
 
     def save(self, idempotency_key=None, request_id=None):
diff --git a/openai/api_resources/abstract/verify_mixin.py b/openai/api_resources/abstract/verify_mixin.py
deleted file mode 100644
index 4deb966ed5..0000000000
--- a/openai/api_resources/abstract/verify_mixin.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from __future__ import absolute_import, division, print_function
-
-from openai import util
-
-
-class VerifyMixin(object):
-    def verify(self, idempotency_key=None, request_id=None, **params):
-        url = self.instance_url() + "/verify"
-        headers = util.populate_headers(idempotency_key, request_id)
-        self.refresh_from(self.request("post", url, params, headers))
-        return self
diff --git a/openai/api_resources/engine.py b/openai/api_resources/engine.py
index 7b62c1c15b..ec2f6c1e87 100644
--- a/openai/api_resources/engine.py
+++ b/openai/api_resources/engine.py
@@ -1,8 +1,6 @@
-from __future__ import absolute_import, division, print_function
-
 import time
 
-from openai import api_requestor, util
+from openai import util
 from openai.api_resources.abstract import (
     ListableAPIResource,
     UpdateableAPIResource,
diff --git a/openai/api_resources/experimental/__init__.py b/openai/api_resources/experimental/__init__.py
index 2288454922..d24c7b0cb5 100644
--- a/openai/api_resources/experimental/__init__.py
+++ b/openai/api_resources/experimental/__init__.py
@@ -1 +1,3 @@
-from openai.api_resources.experimental.completion_config import CompletionConfig
+from openai.api_resources.experimental.completion_config import (  # noqa: F401
+    CompletionConfig,
+)
diff --git a/openai/api_resources/fine_tune.py b/openai/api_resources/fine_tune.py
index 029e6606c4..9597c2f4f3 100644
--- a/openai/api_resources/fine_tune.py
+++ b/openai/api_resources/fine_tune.py
@@ -1,9 +1,10 @@
+from urllib.parse import quote_plus
+
 from openai.api_resources.abstract import (
     ListableAPIResource,
     CreateableAPIResource,
     nested_resource_class_methods,
 )
-from openai.six.moves.urllib.parse import quote_plus
 from openai import api_requestor, util
 
 
@@ -29,7 +30,7 @@ def stream_events(
         request_id=None,
         api_version=None,
         organization=None,
-        **params
+        **params,
     ):
         base = cls.class_url()
         extn = quote_plus(id)
diff --git a/openai/api_resources/list_object.py b/openai/api_resources/list_object.py
deleted file mode 100644
index 9696b67b40..0000000000
--- a/openai/api_resources/list_object.py
+++ /dev/null
@@ -1,176 +0,0 @@
-from __future__ import absolute_import, division, print_function
-
-from openai import api_requestor, six, util
-from openai.openai_object import OpenAIObject
-
-from openai.six.moves.urllib.parse import quote_plus
-
-
-class ListObject(OpenAIObject):
-    OBJECT_NAME = "list"
-
-    def list(self, api_key=None, openai_version=None, openai_account=None, **params):
-        openai_object = self._request(
-            "get",
-            self.get("url"),
-            api_key=api_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            **params
-        )
-        openai_object._retrieve_params = params
-        return openai_object
-
-    def create(
-        self,
-        api_key=None,
-        idempotency_key=None,
-        openai_version=None,
-        openai_account=None,
-        **params
-    ):
-        return self._request(
-            "post",
-            self.get("url"),
-            api_key=api_key,
-            idempotency_key=idempotency_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            **params
-        )
-
-    def retrieve(
-        self, id, api_key=None, openai_version=None, openai_account=None, **params
-    ):
-        url = "%s/%s" % (self.get("url"), quote_plus(util.utf8(id)))
-        return self._request(
-            "get",
-            url,
-            api_key=api_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            **params
-        )
-
-    def _request(
-        self,
-        method_,
-        url_,
-        api_key=None,
-        idempotency_key=None,
-        openai_version=None,
-        openai_account=None,
-        **params
-    ):
-        api_key = api_key or self.api_key
-        openai_version = openai_version or self.openai_version
-        openai_account = openai_account or self.openai_account
-
-        requestor = api_requestor.APIRequestor(
-            api_key, api_version=openai_version, account=openai_account
-        )
-        headers = util.populate_headers(idempotency_key)
-        response, api_key = requestor.request(method_, url_, params, headers)
-        openai_object = util.convert_to_openai_object(
-            response, api_key, openai_version, openai_account
-        )
-        return openai_object
-
-    def __getitem__(self, k):
-        if isinstance(k, six.string_types):
-            return super(ListObject, self).__getitem__(k)
-        else:
-            raise KeyError(
-                "You tried to access the %s index, but ListObject types only "
-                "support string keys. (HINT: List calls return an object with "
-                "a 'data' (which is the data array). You likely want to call "
-                ".data[%s])" % (repr(k), repr(k))
-            )
-
-    def __iter__(self):
-        return getattr(self, "data", []).__iter__()
-
-    def __len__(self):
-        return getattr(self, "data", []).__len__()
-
-    def __reversed__(self):
-        return getattr(self, "data", []).__reversed__()
-
-    def auto_paging_iter(self):
-        page = self
-
-        while True:
-            if (
-                "ending_before" in self._retrieve_params
-                and "starting_after" not in self._retrieve_params
-            ):
-                for item in reversed(page):
-                    yield item
-                page = page.previous_page()
-            else:
-                for item in page:
-                    yield item
-                page = page.next_page()
-
-            if page.is_empty:
-                break
-
-    @classmethod
-    def empty_list(cls, api_key=None, openai_version=None, openai_account=None):
-        return cls.construct_from(
-            {"data": []},
-            key=api_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            last_response=None,
-        )
-
-    @property
-    def is_empty(self):
-        return not self.data
-
-    def next_page(
-        self, api_key=None, openai_version=None, openai_account=None, **params
-    ):
-        if not self.has_more:
-            return self.empty_list(
-                api_key=api_key,
-                openai_version=openai_version,
-                openai_account=openai_account,
-            )
-
-        last_id = self.data[-1].id
-
-        params_with_filters = self._retrieve_params.copy()
-        params_with_filters.update({"starting_after": last_id})
-        params_with_filters.update(params)
-
-        return self.list(
-            api_key=api_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            **params_with_filters
-        )
-
-    def previous_page(
-        self, api_key=None, openai_version=None, openai_account=None, **params
-    ):
-        if not self.has_more:
-            return self.empty_list(
-                api_key=api_key,
-                openai_version=openai_version,
-                openai_account=openai_account,
-            )
-
-        first_id = self.data[0].id
-
-        params_with_filters = self._retrieve_params.copy()
-        params_with_filters.update({"ending_before": first_id})
-        params_with_filters.update(params)
-
-        return self.list(
-            api_key=api_key,
-            openai_version=openai_version,
-            openai_account=openai_account,
-            **params_with_filters
-        )
diff --git a/openai/api_resources/snapshot.py b/openai/api_resources/snapshot.py
index a8cd46d92e..41ab51521f 100644
--- a/openai/api_resources/snapshot.py
+++ b/openai/api_resources/snapshot.py
@@ -1,4 +1,3 @@
-from openai.api_resources.abstract.engine_api_resource import EngineAPIResource
 from openai.api_resources.abstract import (
     ListableAPIResource,
     DeletableAPIResource,
diff --git a/openai/cli.py b/openai/cli.py
index 3a644f4f05..f91f662835 100644
--- a/openai/cli.py
+++ b/openai/cli.py
@@ -285,7 +285,7 @@ def get(cls, args):
     @classmethod
     def events(cls, args):
         if not args.stream:
-            resp = openai.FineTune.list_events(id=args.id)
+            resp = openai.FineTune.list_events(id=args.id)  # type: ignore
             print(resp)
             return
         cls._stream_events(args.id)
@@ -420,7 +420,7 @@ def help(args):
     sub.add_argument("-q", "--query", required=True, help="Search query")
     sub.set_defaults(func=Engine.search)
 
-    ## Completions
+    # Completions
     sub = subparsers.add_parser("completions.create")
     sub.add_argument("-e", "--engine", required=True, help="The engine to use")
     sub.add_argument(
@@ -462,7 +462,7 @@ def help(args):
     )
     sub.set_defaults(func=Completion.create)
 
-    ## Snapshots
+    # Snapshots
     sub = subparsers.add_parser("snapshots.list")
     sub.set_defaults(func=Snapshot.list)
 
diff --git a/openai/error.py b/openai/error.py
index ca4b3b7d99..9683f66124 100644
--- a/openai/error.py
+++ b/openai/error.py
@@ -1,10 +1,8 @@
 from __future__ import absolute_import, division, print_function
 
 import openai
-from openai.six import python_2_unicode_compatible
 
 
-@python_2_unicode_compatible
 class OpenAIError(Exception):
     def __init__(
         self,
@@ -39,7 +37,7 @@ def __init__(
     def __str__(self):
         msg = self._message or "<empty message>"
         if self.request_id is not None:
-            return u"Request {0}: {1}".format(self.request_id, msg)
+            return "Request {0}: {1}".format(self.request_id, msg)
         else:
             return msg
 
@@ -97,40 +95,11 @@ def __init__(
         self.should_retry = should_retry
 
 
-class OpenAIErrorWithParamCode(OpenAIError):
-    def __repr__(self):
-        return "%s(message=%r, param=%r, code=%r, http_status=%r, " "request_id=%r)" % (
-            self.__class__.__name__,
-            self._message,
-            self.param,
-            self.code,
-            self.http_status,
-            self.request_id,
-        )
-
-
-class CardError(OpenAIErrorWithParamCode):
-    def __init__(
-        self,
-        message,
-        param,
-        code,
-        http_body=None,
-        http_status=None,
-        json_body=None,
-        headers=None,
-    ):
-        super(CardError, self).__init__(
-            message, http_body, http_status, json_body, headers, code
-        )
-        self.param = param
-
-
 class IdempotencyError(OpenAIError):
     pass
 
 
-class InvalidRequestError(OpenAIErrorWithParamCode):
+class InvalidRequestError(OpenAIError):
     def __init__(
         self,
         message,
@@ -146,6 +115,16 @@ def __init__(
         )
         self.param = param
 
+    def __repr__(self):
+        return "%s(message=%r, param=%r, code=%r, http_status=%r, " "request_id=%r)" % (
+            self.__class__.__name__,
+            self._message,
+            self.param,
+            self.code,
+            self.http_status,
+            self.request_id,
+        )
+
 
 class AuthenticationError(OpenAIError):
     pass
diff --git a/openai/http_client.py b/openai/http_client.py
index 9927179324..0d256c8b16 100644
--- a/openai/http_client.py
+++ b/openai/http_client.py
@@ -1,93 +1,28 @@
-from __future__ import absolute_import, division, print_function
-
-import sys
-import textwrap
-import warnings
-import email
-import time
+import abc
+import json
 import random
+import textwrap
 import threading
-import json
+import time
+from typing import Any, Dict
+
+import requests
+from urllib.parse import urlparse
 
 import openai
-from openai import error, util, six
+from openai import error, util
 from openai.request_metrics import RequestMetrics
 
-# - Requests is the preferred HTTP library
-# - Google App Engine has urlfetch
-# - Use Pycurl if it's there (at least it verifies SSL certs)
-# - Fall back to urllib2 with a warning if needed
-try:
-    from openai.six.moves import urllib
-except ImportError:
-    # Try to load in urllib2, but don't sweat it if it's not available.
-    pass
-
-try:
-    import pycurl
-except ImportError:
-    pycurl = None
-
-try:
-    import requests
-except ImportError:
-    requests = None
-else:
-    try:
-        # Require version 0.8.8, but don't want to depend on distutils
-        version = requests.__version__
-        major, minor, patch = [int(i) for i in version.split(".")]
-    except Exception:
-        # Probably some new-fangled version, so it should support verify
-        pass
-    else:
-        if (major, minor, patch) < (0, 8, 8):
-            sys.stderr.write(
-                "Warning: the OpenAI library requires that your Python "
-                '"requests" library be newer than version 0.8.8, but your '
-                '"requests" library is version %s. OpenAI will fall back to '
-                "an alternate HTTP library so everything should work. We "
-                'recommend upgrading your "requests" library. If you have any '
-                "questions, please contact support@openai.com. (HINT: running "
-                '"pip install -U requests" should upgrade your requests '
-                "library to the latest version.)" % (version,)
-            )
-            requests = None
-
-try:
-    from google.appengine.api import urlfetch
-except ImportError:
-    urlfetch = None
-
-# proxy support for the pycurl client
-from openai.six.moves.urllib.parse import urlparse
-
 
 def _now_ms():
     return int(round(time.time() * 1000))
 
 
 def new_default_http_client(*args, **kwargs):
-    if urlfetch:
-        impl = UrlFetchClient
-    elif requests:
-        impl = RequestsClient
-    elif pycurl:
-        impl = PycurlClient
-    else:
-        impl = Urllib2Client
-        warnings.warn(
-            "Warning: the OpenAI library is falling back to urllib2/urllib "
-            "because neither requests nor pycurl are installed. "
-            "urllib2's SSL implementation doesn't verify server "
-            "certificates. For improved security, we suggest installing "
-            "requests."
-        )
+    return RequestsClient(*args, **kwargs)
 
-    return impl(*args, **kwargs)
 
-
-class HTTPClient(object):
+class HTTPClient(abc.ABC):
     MAX_DELAY = 2
     INITIAL_DELAY = 0.5
     MAX_RETRY_AFTER = 60
@@ -148,6 +83,7 @@ def request_with_retries(self, method, url, headers, post_data=None, stream=Fals
 
                     return response
                 else:
+                    assert connection_error is not None
                     raise connection_error
 
     def request(self, method, url, headers, post_data=None, stream=False):
@@ -247,8 +183,9 @@ def _record_request_metrics(self, response, request_start):
                 request_id, request_duration_ms
             )
 
+    @abc.abstractmethod
     def close(self):
-        raise NotImplementedError("HTTPClient subclasses must implement `close`")
+        ...
 
 
 class RequestsClient(HTTPClient):
@@ -260,7 +197,7 @@ def __init__(self, timeout=600, session=None, **kwargs):
         self._timeout = timeout
 
     def request(self, method, url, headers, post_data=None, stream=False):
-        kwargs = {}
+        kwargs: Dict[str, Any] = {}
         if self._verify_ssl_certs:
             kwargs["verify"] = openai.ca_bundle_path
         else:
@@ -281,7 +218,7 @@ def request(self, method, url, headers, post_data=None, stream=False):
                     data=post_data,
                     timeout=self._timeout,
                     stream=stream,
-                    **kwargs
+                    **kwargs,
                 )
             except TypeError as e:
                 raise TypeError(
@@ -314,7 +251,7 @@ def _handle_request_error(self, e):
         # but we don't want to retry, unless it is caused by dropped
         # SSL connection
         if isinstance(e, requests.exceptions.SSLError):
-            if 'ECONNRESET' not in repr(e):
+            if "ECONNRESET" not in repr(e):
                 msg = (
                     "Could not verify OpenAI's SSL certificate.  Please make "
                     "sure that your network is not intercepting certificates.  "
@@ -377,258 +314,3 @@ def _sanitized_url(url):
     def close(self):
         if getattr(self._thread_local, "session", None) is not None:
             self._thread_local.session.close()
-
-
-class UrlFetchClient(HTTPClient):
-    name = "urlfetch"
-
-    def __init__(self, verify_ssl_certs=True, proxy=None, deadline=55):
-        super(UrlFetchClient, self).__init__(
-            verify_ssl_certs=verify_ssl_certs, proxy=proxy
-        )
-
-        # no proxy support in urlfetch. for a patch, see:
-        # https://code.google.com/p/googleappengine/issues/detail?id=544
-        if proxy:
-            raise ValueError(
-                "No proxy support in urlfetch library. "
-                "Set openai.default_http_client to either RequestsClient, "
-                "PycurlClient, or Urllib2Client instance to use a proxy."
-            )
-
-        self._verify_ssl_certs = verify_ssl_certs
-        # GAE requests time out after 60 seconds, so make sure to default
-        # to 55 seconds to allow for a slow OpenAI
-        self._deadline = deadline
-
-    def request(self, method, url, headers, post_data=None, stream=False):
-        if stream:
-            raise NotImplementedError("Stream not yet implemented for {}".format(self))
-        try:
-            result = urlfetch.fetch(
-                url=url,
-                method=method,
-                headers=headers,
-                # Google App Engine doesn't let us specify our own cert bundle.
-                # However, that's ok because the CA bundle they use recognizes
-                # api.openai.com.
-                validate_certificate=self._verify_ssl_certs,
-                deadline=self._deadline,
-                payload=post_data,
-            )
-        except urlfetch.Error as e:
-            self._handle_request_error(e, url)
-
-        return result.content, result.status_code, result.headers, stream
-
-    def _handle_request_error(self, e, url):
-        if isinstance(e, urlfetch.InvalidURLError):
-            msg = (
-                "The OpenAI library attempted to fetch an "
-                "invalid URL (%r). This is likely due to a bug "
-                "in the OpenAI Python bindings. Please let us know "
-                "at support@openai.com." % (url,)
-            )
-        elif isinstance(e, urlfetch.DownloadError):
-            msg = "There was a problem retrieving data from OpenAI."
-        elif isinstance(e, urlfetch.ResponseTooLargeError):
-            msg = (
-                "There was a problem receiving all of your data from "
-                "OpenAI.  This is likely due to a bug in OpenAI. "
-                "Please let us know at support@openai.com."
-            )
-        else:
-            msg = (
-                "Unexpected error communicating with OpenAI. If this "
-                "problem persists, let us know at support@openai.com."
-            )
-
-        msg = textwrap.fill(msg) + "\n\n(Network error: " + str(e) + ")"
-        raise error.APIConnectionError(msg)
-
-    def close(self):
-        pass
-
-
-class PycurlClient(HTTPClient):
-    name = "pycurl"
-
-    def __init__(self, verify_ssl_certs=True, proxy=None):
-        super(PycurlClient, self).__init__(
-            verify_ssl_certs=verify_ssl_certs, proxy=proxy
-        )
-
-        # Initialize this within the object so that we can reuse connections.
-        self._curl = pycurl.Curl()
-
-        # need to urlparse the proxy, since PyCurl
-        # consumes the proxy url in small pieces
-        if self._proxy:
-            # now that we have the parser, get the proxy url pieces
-            proxy = self._proxy
-            for scheme, value in six.iteritems(proxy):
-                proxy[scheme] = urlparse(value)
-
-    def parse_headers(self, data):
-        if "\r\n" not in data:
-            return {}
-        raw_headers = data.split("\r\n", 1)[1]
-        headers = email.message_from_string(raw_headers)
-        return dict((k.lower(), v) for k, v in six.iteritems(dict(headers)))
-
-    def request(self, method, url, headers, post_data=None, stream=False):
-        if stream:
-            raise NotImplementedError("Stream not yet implemented for {}".format(self))
-        b = util.io.BytesIO()
-        rheaders = util.io.BytesIO()
-
-        # Pycurl's design is a little weird: although we set per-request
-        # options on this object, it's also capable of maintaining established
-        # connections. Here we call reset() between uses to make sure it's in a
-        # pristine state, but notably reset() doesn't reset connections, so we
-        # still get to take advantage of those by virtue of re-using the same
-        # object.
-        self._curl.reset()
-
-        proxy = self._get_proxy(url)
-        if proxy:
-            if proxy.hostname:
-                self._curl.setopt(pycurl.PROXY, proxy.hostname)
-            if proxy.port:
-                self._curl.setopt(pycurl.PROXYPORT, proxy.port)
-            if proxy.username or proxy.password:
-                self._curl.setopt(
-                    pycurl.PROXYUSERPWD, "%s:%s" % (proxy.username, proxy.password)
-                )
-
-        if method == "get":
-            self._curl.setopt(pycurl.HTTPGET, 1)
-        elif method == "post":
-            self._curl.setopt(pycurl.POST, 1)
-            self._curl.setopt(pycurl.POSTFIELDS, post_data)
-        else:
-            self._curl.setopt(pycurl.CUSTOMREQUEST, method.upper())
-
-        # pycurl doesn't like unicode URLs
-        self._curl.setopt(pycurl.URL, util.utf8(url))
-
-        self._curl.setopt(pycurl.WRITEFUNCTION, b.write)
-        self._curl.setopt(pycurl.HEADERFUNCTION, rheaders.write)
-        self._curl.setopt(pycurl.NOSIGNAL, 1)
-        self._curl.setopt(pycurl.CONNECTTIMEOUT, 30)
-        self._curl.setopt(pycurl.TIMEOUT, 80)
-        self._curl.setopt(
-            pycurl.HTTPHEADER,
-            ["%s: %s" % (k, v) for k, v in six.iteritems(dict(headers))],
-        )
-        if self._verify_ssl_certs:
-            self._curl.setopt(pycurl.CAINFO, openai.ca_bundle_path)
-        else:
-            self._curl.setopt(pycurl.SSL_VERIFYHOST, False)
-
-        try:
-            self._curl.perform()
-        except pycurl.error as e:
-            self._handle_request_error(e)
-        rbody = b.getvalue().decode("utf-8")
-        rcode = self._curl.getinfo(pycurl.RESPONSE_CODE)
-        headers = self.parse_headers(rheaders.getvalue().decode("utf-8"))
-
-        return rbody, rcode, headers, stream
-
-    def _handle_request_error(self, e):
-        if e.args[0] in [
-            pycurl.E_COULDNT_CONNECT,
-            pycurl.E_COULDNT_RESOLVE_HOST,
-            pycurl.E_OPERATION_TIMEOUTED,
-        ]:
-            msg = (
-                "Could not connect to OpenAI.  Please check your "
-                "internet connection and try again.  If this problem "
-                "persists, you should check OpenAI's service status at "
-                "https://twitter.com/openaistatus, or let us know at "
-                "support@openai.com."
-            )
-            should_retry = True
-        elif e.args[0] in [pycurl.E_SSL_CACERT, pycurl.E_SSL_PEER_CERTIFICATE]:
-            msg = (
-                "Could not verify OpenAI's SSL certificate.  Please make "
-                "sure that your network is not intercepting certificates.  "
-                "If this problem persists, let us know at "
-                "support@openai.com."
-            )
-            should_retry = False
-        else:
-            msg = (
-                "Unexpected error communicating with OpenAI. If this "
-                "problem persists, let us know at support@openai.com."
-            )
-            should_retry = False
-
-        msg = textwrap.fill(msg) + "\n\n(Network error: " + e.args[1] + ")"
-        raise error.APIConnectionError(msg, should_retry=should_retry)
-
-    def _get_proxy(self, url):
-        if self._proxy:
-            proxy = self._proxy
-            scheme = url.split(":")[0] if url else None
-            if scheme:
-                return proxy.get(scheme, proxy.get(scheme[0:-1]))
-        return None
-
-    def close(self):
-        pass
-
-
-class Urllib2Client(HTTPClient):
-    name = "urllib.request"
-
-    def __init__(self, verify_ssl_certs=True, proxy=None):
-        super(Urllib2Client, self).__init__(
-            verify_ssl_certs=verify_ssl_certs, proxy=proxy
-        )
-        # prepare and cache proxy tied opener here
-        self._opener = None
-        if self._proxy:
-            proxy = urllib.request.ProxyHandler(self._proxy)
-            self._opener = urllib.request.build_opener(proxy)
-
-    def request(self, method, url, headers, post_data=None, stream=False):
-        if stream:
-            raise NotImplementedError("Stream not yet implemented for {}".format(self))
-        if six.PY3 and isinstance(post_data, six.string_types):
-            post_data = post_data.encode("utf-8")
-
-        req = urllib.request.Request(url, post_data, headers)
-
-        if method not in ("get", "post"):
-            req.get_method = lambda: method.upper()
-
-        try:
-            # use the custom proxy tied opener, if any.
-            # otherwise, fall to the default urllib opener.
-            response = (
-                self._opener.open(req) if self._opener else urllib.request.urlopen(req)
-            )
-            rbody = response.read()
-            rcode = response.code
-            headers = dict(response.info())
-        except urllib.error.HTTPError as e:
-            rcode = e.code
-            rbody = e.read()
-            headers = dict(e.info())
-        except (urllib.error.URLError, ValueError) as e:
-            self._handle_request_error(e)
-        lh = dict((k.lower(), v) for k, v in six.iteritems(dict(headers)))
-        return rbody, rcode, lh, stream
-
-    def _handle_request_error(self, e):
-        msg = (
-            "Unexpected error communicating with OpenAI. "
-            "If this problem persists, let us know at support@openai.com."
-        )
-        msg = textwrap.fill(msg) + "\n\n(Network error: " + str(e) + ")"
-        raise error.APIConnectionError(msg)
-
-    def close(self):
-        pass
diff --git a/openai/multipart_data_generator.py b/openai/multipart_data_generator.py
index 93a683ee7b..43c37c25f2 100644
--- a/openai/multipart_data_generator.py
+++ b/openai/multipart_data_generator.py
@@ -22,7 +22,7 @@ def add_params(self, params):
         # Flatten parameters first
         params = dict(openai.api_requestor._api_encode(params))
 
-        for key, value in openai.six.iteritems(params):
+        for key, value in params.items():
 
             # strip array elements if present from key
             key = self._remove_array_element(key)
@@ -38,7 +38,7 @@ def add_params(self, params):
                     # Convert the filename to string, just in case it's not
                     # already one. E.g. `tempfile.TemporaryFile` has a `name`
                     # attribute but it's an `int`.
-                    filename = openai.six.text_type(value.name)
+                    filename = str(value.name)
 
                 self._write('Content-Disposition: form-data; name="')
                 self._write(key)
@@ -70,9 +70,9 @@ def get_post_data(self):
         return self.data.getvalue()
 
     def _write(self, value):
-        if isinstance(value, openai.six.binary_type):
+        if isinstance(value, bytes):
             array = bytearray(value)
-        elif isinstance(value, openai.six.text_type):
+        elif isinstance(value, str):
             array = bytearray(value, encoding="utf-8")
         else:
             raise TypeError(
diff --git a/openai/openai_object.py b/openai/openai_object.py
index 1f70915305..109741665f 100644
--- a/openai/openai_object.py
+++ b/openai/openai_object.py
@@ -5,7 +5,7 @@
 from copy import deepcopy
 
 import openai
-from openai import api_requestor, util, six
+from openai import api_requestor, util
 
 
 def _compute_diff(current, previous):
@@ -51,7 +51,7 @@ def __init__(
         last_response=None,
         api_base=None,
         engine=None,
-        **params
+        **params,
     ):
         super(OpenAIObject, self).__init__()
 
@@ -218,7 +218,7 @@ def refresh_from(
 
         self._transient_values = self._transient_values - set(values)
 
-        for k, v in six.iteritems(values):
+        for k, v in values.items():
             super(OpenAIObject, self).__setitem__(
                 k, util.convert_to_openai_object(v, api_key, api_version, organization)
             )
@@ -267,10 +267,11 @@ def request(
     def __repr__(self):
         ident_parts = [type(self).__name__]
 
-        if isinstance(self.get("object"), six.string_types):
-            ident_parts.append(self.get("object"))
+        obj = self.get("object")
+        if isinstance(obj, str):
+            ident_parts.append(obj)
 
-        if isinstance(self.get("id"), six.string_types):
+        if isinstance(self.get("id"), str):
             ident_parts.append("id=%s" % (self.get("id"),))
 
         unicode_repr = "<%s at %s> JSON: %s" % (
@@ -279,10 +280,7 @@ def __repr__(self):
             str(self),
         )
 
-        if six.PY2:
-            return unicode_repr.encode("utf-8")
-        else:
-            return unicode_repr
+        return unicode_repr
 
     def __str__(self):
         obj = self.to_dict_recursive()
@@ -293,7 +291,7 @@ def to_dict(self):
 
     def to_dict_recursive(self):
         d = dict(self)
-        for k, v in six.iteritems(d):
+        for k, v in d.items():
             if isinstance(v, OpenAIObject):
                 d[k] = v.to_dict_recursive()
             elif isinstance(v, list):
@@ -312,7 +310,7 @@ def serialize(self, previous):
         unsaved_keys = self._unsaved_values or set()
         previous = previous or self._previous or {}
 
-        for k, v in six.iteritems(self):
+        for k, v in self.items():
             if k == "id" or (isinstance(k, str) and k.startswith("_")):
                 continue
             elif isinstance(v, openai.api_resources.abstract.APIResource):
@@ -343,7 +341,7 @@ def __copy__(self):
 
         copied._retrieve_params = self._retrieve_params
 
-        for k, v in six.iteritems(self):
+        for k, v in self.items():
             # Call parent's __setitem__ to avoid checks that we've added in the
             # overridden version that can throw exceptions.
             super(OpenAIObject, copied).__setitem__(k, v)
@@ -359,7 +357,7 @@ def __deepcopy__(self, memo):
         copied = self.__copy__()
         memo[id(self)] = copied
 
-        for k, v in six.iteritems(self):
+        for k, v in self.items():
             # Call parent's __setitem__ to avoid checks that we've added in the
             # overridden version that can throw exceptions.
             super(OpenAIObject, copied).__setitem__(k, deepcopy(v, memo))
diff --git a/openai/py.typed b/openai/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openai/six.py b/openai/six.py
deleted file mode 100644
index 4c303ff973..0000000000
--- a/openai/six.py
+++ /dev/null
@@ -1,953 +0,0 @@
-# Copyright (c) 2010-2019 Benjamin Peterson
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-"""Utilities for writing code that runs on Python 2 and 3"""
-
-from __future__ import absolute_import
-
-import functools
-import itertools
-import operator
-import sys
-import types
-
-__author__ = "Benjamin Peterson <benjamin@python.org>"
-__version__ = "1.12.0"
-
-
-# Useful for very coarse version differentiation.
-PY2 = sys.version_info[0] == 2
-PY3 = sys.version_info[0] == 3
-PY34 = sys.version_info[0:2] >= (3, 4)
-
-if PY3:
-    string_types = str,
-    integer_types = int,
-    class_types = type,
-    text_type = str
-    binary_type = bytes
-
-    MAXSIZE = sys.maxsize
-else:
-    string_types = basestring,
-    integer_types = (int, long)
-    class_types = (type, types.ClassType)
-    text_type = unicode
-    binary_type = str
-
-    if sys.platform.startswith("java"):
-        # Jython always uses 32 bits.
-        MAXSIZE = int((1 << 31) - 1)
-    else:
-        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
-        class X(object):
-
-            def __len__(self):
-                return 1 << 31
-        try:
-            len(X())
-        except OverflowError:
-            # 32-bit
-            MAXSIZE = int((1 << 31) - 1)
-        else:
-            # 64-bit
-            MAXSIZE = int((1 << 63) - 1)
-        del X
-
-
-def _add_doc(func, doc):
-    """Add documentation to a function."""
-    func.__doc__ = doc
-
-
-def _import_module(name):
-    """Import module, returning the module after the last dot."""
-    __import__(name)
-    return sys.modules[name]
-
-
-class _LazyDescr(object):
-
-    def __init__(self, name):
-        self.name = name
-
-    def __get__(self, obj, tp):
-        result = self._resolve()
-        setattr(obj, self.name, result)  # Invokes __set__.
-        try:
-            # This is a bit ugly, but it avoids running this again by
-            # removing this descriptor.
-            delattr(obj.__class__, self.name)
-        except AttributeError:
-            pass
-        return result
-
-
-class MovedModule(_LazyDescr):
-
-    def __init__(self, name, old, new=None):
-        super(MovedModule, self).__init__(name)
-        if PY3:
-            if new is None:
-                new = name
-            self.mod = new
-        else:
-            self.mod = old
-
-    def _resolve(self):
-        return _import_module(self.mod)
-
-    def __getattr__(self, attr):
-        _module = self._resolve()
-        value = getattr(_module, attr)
-        setattr(self, attr, value)
-        return value
-
-
-class _LazyModule(types.ModuleType):
-
-    def __init__(self, name):
-        super(_LazyModule, self).__init__(name)
-        self.__doc__ = self.__class__.__doc__
-
-    def __dir__(self):
-        attrs = ["__doc__", "__name__"]
-        attrs += [attr.name for attr in self._moved_attributes]
-        return attrs
-
-    # Subclasses should override this
-    _moved_attributes = []
-
-
-class MovedAttribute(_LazyDescr):
-
-    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
-        super(MovedAttribute, self).__init__(name)
-        if PY3:
-            if new_mod is None:
-                new_mod = name
-            self.mod = new_mod
-            if new_attr is None:
-                if old_attr is None:
-                    new_attr = name
-                else:
-                    new_attr = old_attr
-            self.attr = new_attr
-        else:
-            self.mod = old_mod
-            if old_attr is None:
-                old_attr = name
-            self.attr = old_attr
-
-    def _resolve(self):
-        module = _import_module(self.mod)
-        return getattr(module, self.attr)
-
-
-class _SixMetaPathImporter(object):
-
-    """
-    A meta path importer to import six.moves and its submodules.
-
-    This class implements a PEP302 finder and loader. It should be compatible
-    with Python 2.5 and all existing versions of Python3
-    """
-
-    def __init__(self, six_module_name):
-        self.name = six_module_name
-        self.known_modules = {}
-
-    def _add_module(self, mod, *fullnames):
-        for fullname in fullnames:
-            self.known_modules[self.name + "." + fullname] = mod
-
-    def _get_module(self, fullname):
-        return self.known_modules[self.name + "." + fullname]
-
-    def find_module(self, fullname, path=None):
-        if fullname in self.known_modules:
-            return self
-        return None
-
-    def __get_module(self, fullname):
-        try:
-            return self.known_modules[fullname]
-        except KeyError:
-            raise ImportError("This loader does not know module " + fullname)
-
-    def load_module(self, fullname):
-        try:
-            # in case of a reload
-            return sys.modules[fullname]
-        except KeyError:
-            pass
-        mod = self.__get_module(fullname)
-        if isinstance(mod, MovedModule):
-            mod = mod._resolve()
-        else:
-            mod.__loader__ = self
-        sys.modules[fullname] = mod
-        return mod
-
-    def is_package(self, fullname):
-        """
-        Return true, if the named module is a package.
-
-        We need this method to get correct spec objects with
-        Python 3.4 (see PEP451)
-        """
-        return hasattr(self.__get_module(fullname), "__path__")
-
-    def get_code(self, fullname):
-        """Return None
-
-        Required, if is_package is implemented"""
-        self.__get_module(fullname)  # eventually raises ImportError
-        return None
-    get_source = get_code  # same as get_code
-
-_importer = _SixMetaPathImporter(__name__)
-
-
-class _MovedItems(_LazyModule):
-
-    """Lazy loading of moved objects"""
-    __path__ = []  # mark as package
-
-
-_moved_attributes = [
-    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
-    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
-    MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
-    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
-    MovedAttribute("intern", "__builtin__", "sys"),
-    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
-    MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
-    MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
-    MovedAttribute("getoutput", "commands", "subprocess"),
-    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
-    MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
-    MovedAttribute("reduce", "__builtin__", "functools"),
-    MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
-    MovedAttribute("StringIO", "StringIO", "io"),
-    MovedAttribute("UserDict", "UserDict", "collections"),
-    MovedAttribute("UserList", "UserList", "collections"),
-    MovedAttribute("UserString", "UserString", "collections"),
-    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
-    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
-    MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
-    MovedModule("builtins", "__builtin__"),
-    MovedModule("configparser", "ConfigParser"),
-    MovedModule("copyreg", "copy_reg"),
-    MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
-    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
-    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
-    MovedModule("http_cookies", "Cookie", "http.cookies"),
-    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
-    MovedModule("html_parser", "HTMLParser", "html.parser"),
-    MovedModule("http_client", "httplib", "http.client"),
-    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
-    MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
-    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
-    MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
-    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
-    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
-    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
-    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
-    MovedModule("cPickle", "cPickle", "pickle"),
-    MovedModule("queue", "Queue"),
-    MovedModule("reprlib", "repr"),
-    MovedModule("socketserver", "SocketServer"),
-    MovedModule("_thread", "thread", "_thread"),
-    MovedModule("tkinter", "Tkinter"),
-    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
-    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
-    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
-    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
-    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
-    MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
-    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
-    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
-    MovedModule("tkinter_colorchooser", "tkColorChooser",
-                "tkinter.colorchooser"),
-    MovedModule("tkinter_commondialog", "tkCommonDialog",
-                "tkinter.commondialog"),
-    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
-    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
-    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
-    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
-                "tkinter.simpledialog"),
-    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
-    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
-    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
-    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
-    MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
-    MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
-]
-# Add windows specific modules.
-if sys.platform == "win32":
-    _moved_attributes += [
-        MovedModule("winreg", "_winreg"),
-    ]
-
-for attr in _moved_attributes:
-    setattr(_MovedItems, attr.name, attr)
-    if isinstance(attr, MovedModule):
-        _importer._add_module(attr, "moves." + attr.name)
-del attr
-
-_MovedItems._moved_attributes = _moved_attributes
-
-moves = _MovedItems(__name__ + ".moves")
-_importer._add_module(moves, "moves")
-
-
-class Module_six_moves_urllib_parse(_LazyModule):
-
-    """Lazy loading of moved objects in six.moves.urllib_parse"""
-
-
-_urllib_parse_moved_attributes = [
-    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
-    MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
-    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
-    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
-    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
-    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
-    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
-    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
-    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
-    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
-    MovedAttribute("quote", "urllib", "urllib.parse"),
-    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
-    MovedAttribute("unquote", "urllib", "urllib.parse"),
-    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
-    MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
-    MovedAttribute("urlencode", "urllib", "urllib.parse"),
-    MovedAttribute("splitquery", "urllib", "urllib.parse"),
-    MovedAttribute("splittag", "urllib", "urllib.parse"),
-    MovedAttribute("splituser", "urllib", "urllib.parse"),
-    MovedAttribute("splitvalue", "urllib", "urllib.parse"),
-    MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
-    MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
-    MovedAttribute("uses_params", "urlparse", "urllib.parse"),
-    MovedAttribute("uses_query", "urlparse", "urllib.parse"),
-    MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
-]
-for attr in _urllib_parse_moved_attributes:
-    setattr(Module_six_moves_urllib_parse, attr.name, attr)
-del attr
-
-Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
-
-_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
-                      "moves.urllib_parse", "moves.urllib.parse")
-
-
-class Module_six_moves_urllib_error(_LazyModule):
-
-    """Lazy loading of moved objects in six.moves.urllib_error"""
-
-
-_urllib_error_moved_attributes = [
-    MovedAttribute("URLError", "urllib2", "urllib.error"),
-    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
-    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
-]
-for attr in _urllib_error_moved_attributes:
-    setattr(Module_six_moves_urllib_error, attr.name, attr)
-del attr
-
-Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
-
-_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
-                      "moves.urllib_error", "moves.urllib.error")
-
-
-class Module_six_moves_urllib_request(_LazyModule):
-
-    """Lazy loading of moved objects in six.moves.urllib_request"""
-
-
-_urllib_request_moved_attributes = [
-    MovedAttribute("urlopen", "urllib2", "urllib.request"),
-    MovedAttribute("install_opener", "urllib2", "urllib.request"),
-    MovedAttribute("build_opener", "urllib2", "urllib.request"),
-    MovedAttribute("pathname2url", "urllib", "urllib.request"),
-    MovedAttribute("url2pathname", "urllib", "urllib.request"),
-    MovedAttribute("getproxies", "urllib", "urllib.request"),
-    MovedAttribute("Request", "urllib2", "urllib.request"),
-    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
-    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
-    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
-    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
-    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
-    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
-    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
-    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
-    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
-    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
-    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
-    MovedAttribute("URLopener", "urllib", "urllib.request"),
-    MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
-    MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
-    MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
-    MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
-]
-for attr in _urllib_request_moved_attributes:
-    setattr(Module_six_moves_urllib_request, attr.name, attr)
-del attr
-
-Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
-
-_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
-                      "moves.urllib_request", "moves.urllib.request")
-
-
-class Module_six_moves_urllib_response(_LazyModule):
-
-    """Lazy loading of moved objects in six.moves.urllib_response"""
-
-
-_urllib_response_moved_attributes = [
-    MovedAttribute("addbase", "urllib", "urllib.response"),
-    MovedAttribute("addclosehook", "urllib", "urllib.response"),
-    MovedAttribute("addinfo", "urllib", "urllib.response"),
-    MovedAttribute("addinfourl", "urllib", "urllib.response"),
-]
-for attr in _urllib_response_moved_attributes:
-    setattr(Module_six_moves_urllib_response, attr.name, attr)
-del attr
-
-Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
-
-_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
-                      "moves.urllib_response", "moves.urllib.response")
-
-
-class Module_six_moves_urllib_robotparser(_LazyModule):
-
-    """Lazy loading of moved objects in six.moves.urllib_robotparser"""
-
-
-_urllib_robotparser_moved_attributes = [
-    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
-]
-for attr in _urllib_robotparser_moved_attributes:
-    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
-del attr
-
-Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
-
-_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
-                      "moves.urllib_robotparser", "moves.urllib.robotparser")
-
-
-class Module_six_moves_urllib(types.ModuleType):
-
-    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
-    __path__ = []  # mark as package
-    parse = _importer._get_module("moves.urllib_parse")
-    error = _importer._get_module("moves.urllib_error")
-    request = _importer._get_module("moves.urllib_request")
-    response = _importer._get_module("moves.urllib_response")
-    robotparser = _importer._get_module("moves.urllib_robotparser")
-
-    def __dir__(self):
-        return ['parse', 'error', 'request', 'response', 'robotparser']
-
-_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
-                      "moves.urllib")
-
-
-def add_move(move):
-    """Add an item to six.moves."""
-    setattr(_MovedItems, move.name, move)
-
-
-def remove_move(name):
-    """Remove item from six.moves."""
-    try:
-        delattr(_MovedItems, name)
-    except AttributeError:
-        try:
-            del moves.__dict__[name]
-        except KeyError:
-            raise AttributeError("no such move, %r" % (name,))
-
-
-if PY3:
-    _meth_func = "__func__"
-    _meth_self = "__self__"
-
-    _func_closure = "__closure__"
-    _func_code = "__code__"
-    _func_defaults = "__defaults__"
-    _func_globals = "__globals__"
-else:
-    _meth_func = "im_func"
-    _meth_self = "im_self"
-
-    _func_closure = "func_closure"
-    _func_code = "func_code"
-    _func_defaults = "func_defaults"
-    _func_globals = "func_globals"
-
-
-try:
-    advance_iterator = next
-except NameError:
-    def advance_iterator(it):
-        return it.next()
-next = advance_iterator
-
-
-try:
-    callable = callable
-except NameError:
-    def callable(obj):
-        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
-
-
-if PY3:
-    def get_unbound_function(unbound):
-        return unbound
-
-    create_bound_method = types.MethodType
-
-    def create_unbound_method(func, cls):
-        return func
-
-    Iterator = object
-else:
-    def get_unbound_function(unbound):
-        return unbound.im_func
-
-    def create_bound_method(func, obj):
-        return types.MethodType(func, obj, obj.__class__)
-
-    def create_unbound_method(func, cls):
-        return types.MethodType(func, None, cls)
-
-    class Iterator(object):
-
-        def next(self):
-            return type(self).__next__(self)
-
-    callable = callable
-_add_doc(get_unbound_function,
-         """Get the function out of a possibly unbound function""")
-
-
-get_method_function = operator.attrgetter(_meth_func)
-get_method_self = operator.attrgetter(_meth_self)
-get_function_closure = operator.attrgetter(_func_closure)
-get_function_code = operator.attrgetter(_func_code)
-get_function_defaults = operator.attrgetter(_func_defaults)
-get_function_globals = operator.attrgetter(_func_globals)
-
-
-if PY3:
-    def iterkeys(d, **kw):
-        return iter(d.keys(**kw))
-
-    def itervalues(d, **kw):
-        return iter(d.values(**kw))
-
-    def iteritems(d, **kw):
-        return iter(d.items(**kw))
-
-    def iterlists(d, **kw):
-        return iter(d.lists(**kw))
-
-    viewkeys = operator.methodcaller("keys")
-
-    viewvalues = operator.methodcaller("values")
-
-    viewitems = operator.methodcaller("items")
-else:
-    def iterkeys(d, **kw):
-        return d.iterkeys(**kw)
-
-    def itervalues(d, **kw):
-        return d.itervalues(**kw)
-
-    def iteritems(d, **kw):
-        return d.iteritems(**kw)
-
-    def iterlists(d, **kw):
-        return d.iterlists(**kw)
-
-    viewkeys = operator.methodcaller("viewkeys")
-
-    viewvalues = operator.methodcaller("viewvalues")
-
-    viewitems = operator.methodcaller("viewitems")
-
-_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
-_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
-_add_doc(iteritems,
-         "Return an iterator over the (key, value) pairs of a dictionary.")
-_add_doc(iterlists,
-         "Return an iterator over the (key, [values]) pairs of a dictionary.")
-
-
-if PY3:
-    def b(s):
-        return s.encode("latin-1")
-
-    def u(s):
-        return s
-    unichr = chr
-    import struct
-    int2byte = struct.Struct(">B").pack
-    del struct
-    byte2int = operator.itemgetter(0)
-    indexbytes = operator.getitem
-    iterbytes = iter
-    import io
-    StringIO = io.StringIO
-    BytesIO = io.BytesIO
-    del io
-    _assertCountEqual = "assertCountEqual"
-    if sys.version_info[1] <= 1:
-        _assertRaisesRegex = "assertRaisesRegexp"
-        _assertRegex = "assertRegexpMatches"
-    else:
-        _assertRaisesRegex = "assertRaisesRegex"
-        _assertRegex = "assertRegex"
-else:
-    def b(s):
-        return s
-    # Workaround for standalone backslash
-
-    def u(s):
-        return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
-    unichr = unichr
-    int2byte = chr
-
-    def byte2int(bs):
-        return ord(bs[0])
-
-    def indexbytes(buf, i):
-        return ord(buf[i])
-    iterbytes = functools.partial(itertools.imap, ord)
-    import StringIO
-    StringIO = BytesIO = StringIO.StringIO
-    _assertCountEqual = "assertItemsEqual"
-    _assertRaisesRegex = "assertRaisesRegexp"
-    _assertRegex = "assertRegexpMatches"
-_add_doc(b, """Byte literal""")
-_add_doc(u, """Text literal""")
-
-
-def assertCountEqual(self, *args, **kwargs):
-    return getattr(self, _assertCountEqual)(*args, **kwargs)
-
-
-def assertRaisesRegex(self, *args, **kwargs):
-    return getattr(self, _assertRaisesRegex)(*args, **kwargs)
-
-
-def assertRegex(self, *args, **kwargs):
-    return getattr(self, _assertRegex)(*args, **kwargs)
-
-
-if PY3:
-    exec_ = getattr(moves.builtins, "exec")
-
-    def reraise(tp, value, tb=None):
-        try:
-            if value is None:
-                value = tp()
-            if value.__traceback__ is not tb:
-                raise value.with_traceback(tb)
-            raise value
-        finally:
-            value = None
-            tb = None
-
-else:
-    def exec_(_code_, _globs_=None, _locs_=None):
-        """Execute code in a namespace."""
-        if _globs_ is None:
-            frame = sys._getframe(1)
-            _globs_ = frame.f_globals
-            if _locs_ is None:
-                _locs_ = frame.f_locals
-            del frame
-        elif _locs_ is None:
-            _locs_ = _globs_
-        exec("""exec _code_ in _globs_, _locs_""")
-
-    exec_("""def reraise(tp, value, tb=None):
-    try:
-        raise tp, value, tb
-    finally:
-        tb = None
-""")
-
-
-if sys.version_info[:2] == (3, 2):
-    exec_("""def raise_from(value, from_value):
-    try:
-        if from_value is None:
-            raise value
-        raise value from from_value
-    finally:
-        value = None
-""")
-elif sys.version_info[:2] > (3, 2):
-    exec_("""def raise_from(value, from_value):
-    try:
-        raise value from from_value
-    finally:
-        value = None
-""")
-else:
-    def raise_from(value, from_value):
-        raise value
-
-
-print_ = getattr(moves.builtins, "print", None)
-if print_ is None:
-    def print_(*args, **kwargs):
-        """The new-style print function for Python 2.4 and 2.5."""
-        fp = kwargs.pop("file", sys.stdout)
-        if fp is None:
-            return
-
-        def write(data):
-            if not isinstance(data, basestring):
-                data = str(data)
-            # If the file has an encoding, encode unicode with it.
-            if (isinstance(fp, file) and
-                    isinstance(data, unicode) and
-                    fp.encoding is not None):
-                errors = getattr(fp, "errors", None)
-                if errors is None:
-                    errors = "strict"
-                data = data.encode(fp.encoding, errors)
-            fp.write(data)
-        want_unicode = False
-        sep = kwargs.pop("sep", None)
-        if sep is not None:
-            if isinstance(sep, unicode):
-                want_unicode = True
-            elif not isinstance(sep, str):
-                raise TypeError("sep must be None or a string")
-        end = kwargs.pop("end", None)
-        if end is not None:
-            if isinstance(end, unicode):
-                want_unicode = True
-            elif not isinstance(end, str):
-                raise TypeError("end must be None or a string")
-        if kwargs:
-            raise TypeError("invalid keyword arguments to print()")
-        if not want_unicode:
-            for arg in args:
-                if isinstance(arg, unicode):
-                    want_unicode = True
-                    break
-        if want_unicode:
-            newline = unicode("\n")
-            space = unicode(" ")
-        else:
-            newline = "\n"
-            space = " "
-        if sep is None:
-            sep = space
-        if end is None:
-            end = newline
-        for i, arg in enumerate(args):
-            if i:
-                write(sep)
-            write(arg)
-        write(end)
-if sys.version_info[:2] < (3, 3):
-    _print = print_
-
-    def print_(*args, **kwargs):
-        fp = kwargs.get("file", sys.stdout)
-        flush = kwargs.pop("flush", False)
-        _print(*args, **kwargs)
-        if flush and fp is not None:
-            fp.flush()
-
-_add_doc(reraise, """Reraise an exception.""")
-
-if sys.version_info[0:2] < (3, 4):
-    def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
-              updated=functools.WRAPPER_UPDATES):
-        def wrapper(f):
-            f = functools.wraps(wrapped, assigned, updated)(f)
-            f.__wrapped__ = wrapped
-            return f
-        return wrapper
-else:
-    wraps = functools.wraps
-
-
-def with_metaclass(meta, *bases):
-    """Create a base class with a metaclass."""
-    # This requires a bit of explanation: the basic idea is to make a dummy
-    # metaclass for one level of class instantiation that replaces itself with
-    # the actual metaclass.
-    class metaclass(type):
-
-        def __new__(cls, name, this_bases, d):
-            return meta(name, bases, d)
-
-        @classmethod
-        def __prepare__(cls, name, this_bases):
-            return meta.__prepare__(name, bases)
-    return type.__new__(metaclass, 'temporary_class', (), {})
-
-
-def add_metaclass(metaclass):
-    """Class decorator for creating a class with a metaclass."""
-    def wrapper(cls):
-        orig_vars = cls.__dict__.copy()
-        slots = orig_vars.get('__slots__')
-        if slots is not None:
-            if isinstance(slots, str):
-                slots = [slots]
-            for slots_var in slots:
-                orig_vars.pop(slots_var)
-        orig_vars.pop('__dict__', None)
-        orig_vars.pop('__weakref__', None)
-        if hasattr(cls, '__qualname__'):
-            orig_vars['__qualname__'] = cls.__qualname__
-        return metaclass(cls.__name__, cls.__bases__, orig_vars)
-    return wrapper
-
-
-def ensure_binary(s, encoding='utf-8', errors='strict'):
-    """Coerce **s** to six.binary_type.
-
-    For Python 2:
-      - `unicode` -> encoded to `str`
-      - `str` -> `str`
-
-    For Python 3:
-      - `str` -> encoded to `bytes`
-      - `bytes` -> `bytes`
-    """
-    if isinstance(s, text_type):
-        return s.encode(encoding, errors)
-    elif isinstance(s, binary_type):
-        return s
-    else:
-        raise TypeError("not expecting type '%s'" % type(s))
-
-
-def ensure_str(s, encoding='utf-8', errors='strict'):
-    """Coerce *s* to `str`.
-
-    For Python 2:
-      - `unicode` -> encoded to `str`
-      - `str` -> `str`
-
-    For Python 3:
-      - `str` -> `str`
-      - `bytes` -> decoded to `str`
-    """
-    if not isinstance(s, (text_type, binary_type)):
-        raise TypeError("not expecting type '%s'" % type(s))
-    if PY2 and isinstance(s, text_type):
-        s = s.encode(encoding, errors)
-    elif PY3 and isinstance(s, binary_type):
-        s = s.decode(encoding, errors)
-    return s
-
-
-def ensure_text(s, encoding='utf-8', errors='strict'):
-    """Coerce *s* to six.text_type.
-
-    For Python 2:
-      - `unicode` -> `unicode`
-      - `str` -> `unicode`
-
-    For Python 3:
-      - `str` -> `str`
-      - `bytes` -> decoded to `str`
-    """
-    if isinstance(s, binary_type):
-        return s.decode(encoding, errors)
-    elif isinstance(s, text_type):
-        return s
-    else:
-        raise TypeError("not expecting type '%s'" % type(s))
-
-
-
-def python_2_unicode_compatible(klass):
-    """
-    A decorator that defines __unicode__ and __str__ methods under Python 2.
-    Under Python 3 it does nothing.
-
-    To support Python 2 and 3 with a single code base, define a __str__ method
-    returning text and apply this decorator to the class.
-    """
-    if PY2:
-        if '__str__' not in klass.__dict__:
-            raise ValueError("@python_2_unicode_compatible cannot be applied "
-                             "to %s because it doesn't define __str__()." %
-                             klass.__name__)
-        klass.__unicode__ = klass.__str__
-        klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
-    return klass
-
-
-# Complete the moves implementation.
-# This code is at the end of this module to speed up module loading.
-# Turn this module into a package.
-__path__ = []  # required for PEP 302 and PEP 451
-__package__ = __name__  # see PEP 366 @ReservedAssignment
-if globals().get("__spec__") is not None:
-    __spec__.submodule_search_locations = []  # PEP 451 @UndefinedVariable
-# Remove other six meta path importers, since they cause problems. This can
-# happen if six is removed from sys.modules and then reloaded. (Setuptools does
-# this for some reason.)
-if sys.meta_path:
-    for i, importer in enumerate(sys.meta_path):
-        # Here's some real nastiness: Another "instance" of the six module might
-        # be floating around. Therefore, we can't use isinstance() to check for
-        # the six meta path importer, since the other six instance will have
-        # inserted an importer with different class.
-        if (type(importer).__name__ == "_SixMetaPathImporter" and
-                importer.name == __name__):
-            del sys.meta_path[i]
-            break
-    del i, importer
-# Finally, add the importer to the meta path import hook.
-sys.meta_path.append(_importer)
diff --git a/openai/tests/test_endpoints.py b/openai/tests/test_endpoints.py
index 25cc1cf764..b6fe681684 100644
--- a/openai/tests/test_endpoints.py
+++ b/openai/tests/test_endpoints.py
@@ -1,9 +1,9 @@
 import openai
 import io
 import json
-import uuid
 
-### FILE TESTS
+
+# FILE TESTS
 def test_file_upload():
     result = openai.File.create(
         file=io.StringIO(json.dumps({"text": "test file data"})),
@@ -13,7 +13,7 @@ def test_file_upload():
     assert "id" in result
 
 
-### COMPLETION TESTS
+# COMPLETION TESTS
 def test_completions():
     result = openai.Completion.create(prompt="This was a test", n=5, engine="davinci")
     assert len(result.choices) == 5
diff --git a/openai/upload_progress.py b/openai/upload_progress.py
index 4214202c63..1d0a1fe6a3 100644
--- a/openai/upload_progress.py
+++ b/openai/upload_progress.py
@@ -1,4 +1,3 @@
-import requests
 import io
 
 
@@ -35,7 +34,7 @@ def read(self, n=-1):
 
 
 def progress(total, desc):
-    import tqdm
+    import tqdm  # type: ignore
 
     meter = tqdm.tqdm(total=total, unit_scale=True, desc=desc)
 
diff --git a/openai/util.py b/openai/util.py
index aef35e412a..5e10292e95 100644
--- a/openai/util.py
+++ b/openai/util.py
@@ -1,16 +1,13 @@
-from __future__ import absolute_import, division, print_function
-
 import functools
 import hmac
 import io
 import logging
-import sys
 import os
 import re
+import sys
+from urllib.parse import parse_qsl
 
 import openai
-from openai import six
-from openai.six.moves.urllib.parse import parse_qsl
 
 
 OPENAI_LOG = os.environ.get("OPENAI_LOG")
@@ -20,7 +17,6 @@
 __all__ = [
     "io",
     "parse_qsl",
-    "utf8",
     "log_info",
     "log_debug",
     "log_warn",
@@ -29,13 +25,6 @@
 ]
 
 
-def utf8(value):
-    if six.PY2 and isinstance(value, six.text_type):
-        return value.encode("utf-8")
-    else:
-        return value
-
-
 def is_appengine_dev():
     return "APPENGINE_RUNTIME" in os.environ and "Dev" in os.environ.get(
         "SERVER_SOFTWARE", ""
@@ -89,52 +78,23 @@ def dashboard_link(request_id):
 def logfmt(props):
     def fmt(key, val):
         # Handle case where val is a bytes or bytesarray
-        if six.PY3 and hasattr(val, "decode"):
+        if hasattr(val, "decode"):
             val = val.decode("utf-8")
-        # Check if val is already a string to avoid re-encoding into
-        # ascii. Since the code is sent through 2to3, we can't just
-        # use unicode(val, encoding='utf8') since it will be
-        # translated incorrectly.
-        if not isinstance(val, six.string_types):
-            val = six.text_type(val)
+        # Check if val is already a string to avoid re-encoding into ascii.
+        if not isinstance(val, str):
+            val = str(val)
         if re.search(r"\s", val):
             val = repr(val)
         # key should already be a string
         if re.search(r"\s", key):
             key = repr(key)
-        return u"{key}={val}".format(key=key, val=val)
-
-    return u" ".join([fmt(key, val) for key, val in sorted(props.items())])
-
-
-# Borrowed from Django's source code
-if hasattr(hmac, "compare_digest"):
-    # Prefer the stdlib implementation, when available.
-    def secure_compare(val1, val2):
-        return hmac.compare_digest(utf8(val1), utf8(val2))
-
-
-else:
-
-    def secure_compare(val1, val2):
-        """
-        Returns True if the two strings are equal, False otherwise.
-        The time taken is independent of the number of characters that match.
-        For the sake of simplicity, this function executes in constant time
-        only when the two strings have the same length. It short-circuits when
-        they have different lengths.
-        """
-        val1, val2 = utf8(val1), utf8(val2)
-        if len(val1) != len(val2):
-            return False
-        result = 0
-        if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes):
-            for x, y in zip(val1, val2):
-                result |= x ^ y
-        else:
-            for x, y in zip(val1, val2):
-                result |= ord(x) ^ ord(y)
-        return result == 0
+        return "{key}={val}".format(key=key, val=val)
+
+    return " ".join([fmt(key, val) for key, val in sorted(props.items())])
+
+
+def secure_compare(val1, val2):
+    return hmac.compare_digest(val1, val2)
 
 
 def get_object_classes():
@@ -179,7 +139,7 @@ def convert_to_openai_object(
     ):
         resp = resp.copy()
         klass_name = resp.get("object")
-        if isinstance(klass_name, six.string_types):
+        if isinstance(klass_name, str):
             klass = get_object_classes().get(
                 klass_name, openai.openai_object.OpenAIObject
             )
@@ -213,7 +173,7 @@ def convert_to_dict(obj):
     # comprehension returns a regular dict and recursively applies the
     # conversion to each value.
     elif isinstance(obj, dict):
-        return {k: convert_to_dict(v) for k, v in six.iteritems(obj)}
+        return {k: convert_to_dict(v) for k, v in obj.items()}
     else:
         return obj
 
diff --git a/openai/version.py b/openai/version.py
index 451e5a51b3..f40610919c 100644
--- a/openai/version.py
+++ b/openai/version.py
@@ -1 +1 @@
-VERSION = "0.6.4"
+VERSION = "0.7.0"
diff --git a/public/setup.py b/public/setup.py
index 4f62eeff0a..0198a53361 100644
--- a/public/setup.py
+++ b/public/setup.py
@@ -1,6 +1,6 @@
 import os
 
-from setuptools import find_packages, setup
+from setuptools import setup
 
 if os.getenv("OPENAI_UPLOAD") != "y":
     raise RuntimeError(
diff --git a/setup.py b/setup.py
index 21d5f70f98..dce57104e7 100644
--- a/setup.py
+++ b/setup.py
@@ -3,9 +3,10 @@
 from setuptools import find_packages, setup
 
 version_contents = {}
-with open(
-    os.path.join(os.path.abspath(os.path.dirname(__file__)), "openai/version.py")
-) as f:
+version_path = os.path.join(
+    os.path.abspath(os.path.dirname(__file__)), "openai/version.py"
+)
+with open(version_path, "rt") as f:
     exec(f.read(), version_contents)
 
 setup(
@@ -13,15 +14,19 @@
     description="Python client library for the OpenAI API",
     version=version_contents["VERSION"],
     install_requires=[
-        'requests >= 2.20; python_version >= "3.0"',
-        'requests[security] >= 2.20; python_version < "3.0"',
+        "requests>=2.20",  # to get the patch for CVE-2018-18074
         "tqdm",  # Needed for progress bars
     ],
     extras_require={"dev": ["black==20.8b1", "pytest==6.*"]},
-    python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+    python_requires=">=3.6",
     scripts=["bin/openai"],
     packages=find_packages(exclude=["tests", "tests.*"]),
-    package_data={"openai": ["data/ca-certificates.crt"]},
+    package_data={
+        "openai": [
+            "data/ca-certificates.crt",
+            "py.typed",
+        ]
+    },
     author="OpenAI",
     author_email="support@openai.com",
     url="https://github.com/openai/openai-python",