Skip to content

Commit 40c32f9

Browse files
Remove support for py <3.6, mypy, lots of cleanup (#19)
* python 3.6 + typing (#25) - Formally require Python 3.6 or later. This never worked under Python 2.7. - Drop now-unnecessary `__future__` and `six` references. - Use `py.typed` to declare that we support type hints. - Fix a variety of mypy errors. - Don't use star imports, so that other packages can tell if they're making an invalid reference. - Drop support for non-`requests` HTTP clients. - Drop `EngineAPIResource.update`; it could never have worked. Tested against primaryapi and engineapi in staging, and it doesn't break them. After this, `mypy ./openai` runs clean. * delete unused code (#27) - Delete `ListObject`. It has some typing errors that suggest it never worked, and it is unused as far as I can tell. - Delete `VerifyMixin`. It is unused. - Delete `CardError`. It is unused and smells of rotten pasta. - Delete `OpenAIErrorWithParamCode`, which only has one subclass, `InvalidRequestError`, and make `InvalidRequestError` a direct subclass of `OpenAIError`. Currrently, `OpenAIErrorWithParamCode` depends on the internal structure of `InvalidRequestError` so they're not independent. * boring formatting and typing fixes (#26) These are another step towards being able to enforce black, flake8, and mypy on CI. * fix more typing issues in prep for CI (#28) - Simplify `platform.XXX` calls. As far as I know these can't raise an exception in Python 3. - Simplify `EngineAPIResource` constructor and remove its unused `retrieve` method. * Update readme, bump version * typo fix Co-authored-by: Madeleine Thompson <[email protected]> Co-authored-by: Madeleine Thompson <[email protected]>
1 parent d53d9ef commit 40c32f9

33 files changed

+153
-1734
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ openai api completions.create -e ada -p "Hello world"
6868

6969
## Requirements
7070

71-
- Python 3.4+
71+
- Python 3.6+
7272

7373
In general we want to support the versions of Python that our
7474
customers are using, so if you run into issues with any version

openai/__init__.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import absolute_import, division, print_function
2-
31
import os
42

53
# OpenAI Python bindings.
@@ -27,6 +25,15 @@
2725
log = None
2826

2927
# API resources
30-
from openai.api_resources import * # noqa
28+
from openai.api_resources import ( # noqa: E402,F401
29+
Answer,
30+
Classification,
31+
Completion,
32+
Engine,
33+
ErrorObject,
34+
File,
35+
FineTune,
36+
Snapshot,
37+
)
3138

32-
from openai.error import OpenAIError, APIError, InvalidRequestError
39+
from openai.error import OpenAIError, APIError, InvalidRequestError # noqa: E402,F401

openai/api_requestor.py

+15-25
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
1-
from __future__ import absolute_import, division, print_function
2-
31
import calendar
42
import datetime
53
import json
64
import platform
75
import time
86
import uuid
97
import warnings
10-
import gzip
118
from io import BytesIO
129
from collections import OrderedDict
10+
from urllib.parse import urlencode, urlsplit, urlunsplit
1311

1412
import openai
15-
from openai import error, http_client, version, util, six
13+
from openai import error, http_client, version, util
1614
from openai.multipart_data_generator import MultipartDataGenerator
17-
from openai.six.moves.urllib.parse import urlencode, urlsplit, urlunsplit
1815
from openai.openai_response import OpenAIResponse
1916
from openai.upload_progress import BufferReader
2017

2118

22-
def _encode_datetime(dttime):
19+
def _encode_datetime(dttime) -> int:
20+
utc_timestamp: float
2321
if dttime.tzinfo and dttime.tzinfo.utcoffset(dttime) is not None:
2422
utc_timestamp = calendar.timegm(dttime.utctimetuple())
2523
else:
@@ -30,14 +28,13 @@ def _encode_datetime(dttime):
3028

3129
def _encode_nested_dict(key, data, fmt="%s[%s]"):
3230
d = OrderedDict()
33-
for subkey, subvalue in six.iteritems(data):
31+
for subkey, subvalue in data.items():
3432
d[fmt % (key, subkey)] = subvalue
3533
return d
3634

3735

3836
def _api_encode(data):
39-
for key, value in six.iteritems(data):
40-
key = util.utf8(key)
37+
for key, value in data.items():
4138
if value is None:
4239
continue
4340
elif hasattr(value, "openai_id"):
@@ -49,15 +46,15 @@ def _api_encode(data):
4946
for k, v in _api_encode(subdict):
5047
yield (k, v)
5148
else:
52-
yield ("%s[%d]" % (key, i), util.utf8(sv))
49+
yield ("%s[%d]" % (key, i), sv)
5350
elif isinstance(value, dict):
5451
subdict = _encode_nested_dict(key, value)
5552
for subkey, subvalue in _api_encode(subdict):
5653
yield (subkey, subvalue)
5754
elif isinstance(value, datetime.datetime):
5855
yield (key, _encode_datetime(value))
5956
else:
60-
yield (key, util.utf8(value))
57+
yield (key, value)
6158

6259

6360
def _build_api_url(url, query):
@@ -81,7 +78,7 @@ def parse_stream(rbody):
8178
yield line
8279

8380

84-
class APIRequestor(object):
81+
class APIRequestor:
8582
def __init__(
8683
self, key=None, client=None, api_base=None, api_version=None, organization=None
8784
):
@@ -205,20 +202,13 @@ def request_headers(self, api_key, method, extra):
205202

206203
ua = {
207204
"bindings_version": version.VERSION,
205+
"httplib": self._client.name,
208206
"lang": "python",
207+
"lang_version": platform.python_version(),
208+
"platform": platform.platform(),
209209
"publisher": "openai",
210-
"httplib": self._client.name,
210+
"uname": " ".join(platform.uname()),
211211
}
212-
for attr, func in [
213-
["lang_version", platform.python_version],
214-
["platform", platform.platform],
215-
["uname", lambda: " ".join(platform.uname())],
216-
]:
217-
try:
218-
val = func()
219-
except Exception as e:
220-
val = "!! %s" % (e,)
221-
ua[attr] = val
222212
if openai.app_info:
223213
ua["application"] = openai.app_info
224214

@@ -257,7 +247,7 @@ def request_raw(
257247

258248
if my_api_key is None:
259249
raise error.AuthenticationError(
260-
"No API key provided. (HINT: set your API key using in code using "
250+
"No API key provided. (HINT: set your API key in code using "
261251
'"openai.api_key = <API-KEY>", or you can set the environment variable OPENAI_API_KEY=<API-KEY>). You can generate API keys '
262252
"in the OpenAI web interface. See https://onboard.openai.com "
263253
"for details, or email [email protected] if you have any "
@@ -320,7 +310,7 @@ def request_raw(
320310

321311
headers = self.request_headers(my_api_key, method, headers)
322312
if supplied_headers is not None:
323-
for key, value in six.iteritems(supplied_headers):
313+
for key, value in supplied_headers.items():
324314
headers[key] = value
325315

326316
util.log_info("Request to OpenAI API", method=method, path=abs_url)

openai/api_resources/__init__.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from openai.api_resources.completion import Completion
2-
from openai.api_resources.engine import Engine
3-
from openai.api_resources.error_object import ErrorObject
4-
from openai.api_resources.file import File
5-
from openai.api_resources.answer import Answer
6-
from openai.api_resources.classification import Classification
7-
from openai.api_resources.snapshot import Snapshot
8-
from openai.api_resources.fine_tune import FineTune
1+
from openai.api_resources.completion import Completion # noqa: F401
2+
from openai.api_resources.engine import Engine # noqa: F401
3+
from openai.api_resources.error_object import ErrorObject # noqa: F401
4+
from openai.api_resources.file import File # noqa: F401
5+
from openai.api_resources.answer import Answer # noqa: F401
6+
from openai.api_resources.classification import Classification # noqa: F401
7+
from openai.api_resources.snapshot import Snapshot # noqa: F401
8+
from openai.api_resources.fine_tune import FineTune # noqa: F401
+5-21
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
1-
from __future__ import absolute_import, division, print_function
2-
31
# flake8: noqa
42

53
from openai.api_resources.abstract.api_resource import APIResource
6-
from openai.api_resources.abstract.singleton_api_resource import (
7-
SingletonAPIResource,
8-
)
9-
10-
from openai.api_resources.abstract.createable_api_resource import (
11-
CreateableAPIResource,
12-
)
13-
from openai.api_resources.abstract.updateable_api_resource import (
14-
UpdateableAPIResource,
15-
)
16-
from openai.api_resources.abstract.deletable_api_resource import (
17-
DeletableAPIResource,
18-
)
19-
from openai.api_resources.abstract.listable_api_resource import (
20-
ListableAPIResource,
21-
)
22-
from openai.api_resources.abstract.verify_mixin import VerifyMixin
23-
4+
from openai.api_resources.abstract.singleton_api_resource import SingletonAPIResource
5+
from openai.api_resources.abstract.createable_api_resource import CreateableAPIResource
6+
from openai.api_resources.abstract.updateable_api_resource import UpdateableAPIResource
7+
from openai.api_resources.abstract.deletable_api_resource import DeletableAPIResource
8+
from openai.api_resources.abstract.listable_api_resource import ListableAPIResource
249
from openai.api_resources.abstract.custom_method import custom_method
25-
2610
from openai.api_resources.abstract.nested_resource_class_methods import (
2711
nested_resource_class_methods,
2812
)

openai/api_resources/abstract/api_resource.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from __future__ import absolute_import, division, print_function
1+
from urllib.parse import quote_plus
22

3-
from openai import api_requestor, error, six, util
3+
from openai import api_requestor, error, util
44
from openai.openai_object import OpenAIObject
5-
from openai.six.moves.urllib.parse import quote_plus
65

76

87
class APIResource(OpenAIObject):
@@ -28,21 +27,20 @@ def class_url(cls):
2827
)
2928
# Namespaces are separated in object names with periods (.) and in URLs
3029
# with forward slashes (/), so replace the former with the latter.
31-
base = cls.OBJECT_NAME.replace(".", "/")
30+
base = cls.OBJECT_NAME.replace(".", "/") # type: ignore
3231
return "/%s/%ss" % (cls.api_prefix, base)
3332

3433
def instance_url(self):
3534
id = self.get("id")
3635

37-
if not isinstance(id, six.string_types):
36+
if not isinstance(id, str):
3837
raise error.InvalidRequestError(
3938
"Could not determine which URL to request: %s instance "
4039
"has invalid ID: %r, %s. ID should be of type `str` (or"
4140
" `unicode`)" % (type(self).__name__, id, type(id)),
4241
"id",
4342
)
4443

45-
id = util.utf8(id)
4644
base = self.class_url()
4745
extn = quote_plus(id)
4846
return "%s/%s" % (base, extn)
@@ -60,7 +58,7 @@ def _static_request(
6058
request_id=None,
6159
api_version=None,
6260
organization=None,
63-
**params
61+
**params,
6462
):
6563
requestor = api_requestor.APIRequestor(
6664
api_key,

openai/api_resources/abstract/createable_api_resource.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def create(
1616
request_id=None,
1717
api_version=None,
1818
organization=None,
19-
**params
19+
**params,
2020
):
2121
requestor = api_requestor.APIRequestor(
2222
api_key,

openai/api_resources/abstract/custom_method.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from __future__ import absolute_import, division, print_function
1+
from urllib.parse import quote_plus
22

33
from openai import util
4-
from openai.six.moves.urllib.parse import quote_plus
54

65

76
def custom_method(name, http_verb, http_path=None):
@@ -17,7 +16,7 @@ def wrapper(cls):
1716
def custom_method_request(cls, sid, **params):
1817
url = "%s/%s/%s" % (
1918
cls.class_url(),
20-
quote_plus(util.utf8(sid)),
19+
quote_plus(sid),
2120
http_path,
2221
)
2322
return cls._static_request(http_verb, url, **params)
@@ -33,9 +32,7 @@ def custom_method_request(cls, sid, **params):
3332
# that the new class method is called when the original method is
3433
# called as a class method.
3534
setattr(cls, "_cls_" + name, classmethod(custom_method_request))
36-
instance_method = util.class_method_variant("_cls_" + name)(
37-
existing_method
38-
)
35+
instance_method = util.class_method_variant("_cls_" + name)(existing_method)
3936
setattr(cls, name, instance_method)
4037

4138
return cls

openai/api_resources/abstract/deletable_api_resource.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
from __future__ import absolute_import, division, print_function
1+
from urllib.parse import quote_plus
22

33
from openai import util
44
from openai.api_resources.abstract.api_resource import APIResource
5-
from openai.six.moves.urllib.parse import quote_plus
65

76

87
class DeletableAPIResource(APIResource):
98
@classmethod
109
def _cls_delete(cls, sid, **params):
11-
url = "%s/%s" % (cls.class_url(), quote_plus(util.utf8(sid)))
10+
url = "%s/%s" % (cls.class_url(), quote_plus(sid))
1211
return cls._static_request("delete", url, **params)
1312

1413
@util.class_method_variant("_cls_delete")

openai/api_resources/abstract/engine_api_resource.py

+8-45
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import time
2+
from typing import Optional
3+
from urllib.parse import quote_plus
24

3-
from openai import api_requestor, error, six, util
5+
from openai import api_requestor, error, util
46
from openai.api_resources.abstract.api_resource import APIResource
5-
from openai.six.moves.urllib.parse import quote_plus
67

78
MAX_TIMEOUT = 20
89

@@ -11,56 +12,20 @@ class EngineAPIResource(APIResource):
1112
engine_required = True
1213
plain_old_data = False
1314

14-
def __init__(self, *args, **kwargs):
15-
engine = kwargs.pop("engine", None)
16-
super().__init__(*args, engine=engine, **kwargs)
15+
def __init__(self, engine: Optional[str] = None, **kwargs):
16+
super().__init__(engine=engine, **kwargs)
1717

1818
@classmethod
19-
def class_url(cls, engine=None):
19+
def class_url(cls, engine: Optional[str] = None):
2020
# Namespaces are separated in object names with periods (.) and in URLs
2121
# with forward slashes (/), so replace the former with the latter.
22-
base = cls.OBJECT_NAME.replace(".", "/")
22+
base = cls.OBJECT_NAME.replace(".", "/") # type: ignore
2323
if engine is None:
2424
return "/%s/%ss" % (cls.api_prefix, base)
2525

26-
engine = util.utf8(engine)
2726
extn = quote_plus(engine)
2827
return "/%s/engines/%s/%ss" % (cls.api_prefix, extn, base)
2928

30-
@classmethod
31-
def retrieve(cls, id, api_key=None, request_id=None, **params):
32-
engine = params.pop("engine", None)
33-
instance = cls(id, api_key, engine=engine, **params)
34-
instance.refresh(request_id=request_id)
35-
return instance
36-
37-
@classmethod
38-
def update(
39-
cls,
40-
api_key=None,
41-
api_base=None,
42-
idempotency_key=None,
43-
request_id=None,
44-
api_version=None,
45-
organization=None,
46-
**params,
47-
):
48-
# TODO max
49-
engine_id = params.get("id")
50-
replicas = params.get("replicas")
51-
52-
engine = EngineAPIResource(id=id)
53-
54-
requestor = api_requestor.APIRequestor(
55-
api_key,
56-
api_base=api_base,
57-
api_version=api_version,
58-
organization=organization,
59-
)
60-
url = cls.class_url(engine)
61-
headers = util.populate_headers(idempotency_key, request_id)
62-
response, _, api_key = requestor.request("post", url, params, headers)
63-
6429
@classmethod
6530
def create(
6631
cls,
@@ -138,15 +103,14 @@ def create(
138103
def instance_url(self):
139104
id = self.get("id")
140105

141-
if not isinstance(id, six.string_types):
106+
if not isinstance(id, str):
142107
raise error.InvalidRequestError(
143108
"Could not determine which URL to request: %s instance "
144109
"has invalid ID: %r, %s. ID should be of type `str` (or"
145110
" `unicode`)" % (type(self).__name__, id, type(id)),
146111
"id",
147112
)
148113

149-
id = util.utf8(id)
150114
base = self.class_url(self.engine)
151115
extn = quote_plus(id)
152116
url = "%s/%s" % (base, extn)
@@ -158,7 +122,6 @@ def instance_url(self):
158122
return url
159123

160124
def wait(self, timeout=None):
161-
engine = self.engine
162125
start = time.time()
163126
while self.status != "complete":
164127
self.timeout = (

0 commit comments

Comments
 (0)