Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Python] Fix Python UTF-8 Encoding Issue #5679

Merged
merged 80 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
cdf8445
Try decoding but don't bail on error
CrshOverride Feb 27, 2020
c2c474e
Switch binary and ByteArray to bytes
CrshOverride Mar 23, 2020
9aec538
Read content type and parse appropriately
CrshOverride Mar 23, 2020
67889ce
Remove response parsing
CrshOverride Mar 23, 2020
298cecc
Remove response parsing and just return the data
CrshOverride Mar 23, 2020
6bff340
Update petshop examples w/ new generator code
CrshOverride Mar 23, 2020
6df4a18
Fix copy/paste error with naming
CrshOverride Mar 23, 2020
a6e9a8b
Update petstore examples
CrshOverride Mar 23, 2020
0e83469
Move response decoding to inside _preload_content block
CrshOverride Mar 23, 2020
8633cbd
Update the clients again
CrshOverride Mar 23, 2020
3729d12
Use a raw string for the regex pattern
CrshOverride Mar 23, 2020
4e3e0f3
Regenerate petstore clients
CrshOverride Mar 23, 2020
d9c9062
Add bytes to python primitives as it's supported in 2.7 and 3
CrshOverride Mar 24, 2020
ce76a48
Add bytes to the exports from model_utils
CrshOverride Mar 24, 2020
97586ff
Import bytes from model_utils
CrshOverride Mar 24, 2020
02f7504
Add conditional typing for regex pattern to match variable type
CrshOverride Mar 24, 2020
ef36e6e
Regenerate petstore clients
CrshOverride Mar 24, 2020
a2aa585
Use read() instead of text() for asyncio
CrshOverride Mar 24, 2020
21652a8
Regenerate petstore clients
CrshOverride Mar 24, 2020
494358e
Remove unused six import
CrshOverride Mar 24, 2020
786df18
Regenerate petstore clients
CrshOverride Mar 24, 2020
dfb9bd7
Add newline to kick Circle to re-run
CrshOverride Mar 24, 2020
0d06ace
Merge branch 'master' of github.com:OpenAPITools/openapi-generator
CrshOverride Mar 24, 2020
53e4c64
Remove whitespace from tox.ini
CrshOverride Mar 25, 2020
577091e
Update more examples after ensure_updated
CrshOverride Mar 25, 2020
7c46996
Merge branch 'master' of github.com:OpenAPITools/openapi-generator
CrshOverride Mar 25, 2020
ac55616
Add sample updates that didn't run with the --batch flag
CrshOverride Mar 25, 2020
f8a7a55
Remove extra bracket in regex to remove warning
CrshOverride Mar 25, 2020
90f5bb0
Merge branch 'master' of github.com:OpenAPITools/openapi-generator
CrshOverride Mar 25, 2020
0cdcc37
Stop printing debug messages
CrshOverride Mar 25, 2020
6b49340
Add bytes examples to python doc generators
CrshOverride Mar 25, 2020
9b1a462
Update generated FakeApi docs
CrshOverride Mar 25, 2020
3615765
Regenerate api_client.py
CrshOverride Mar 25, 2020
9f146ff
Remove print statements from generated clients
CrshOverride Mar 25, 2020
94182ec
Update bytes example in FakeApi.md. Again. I swear.
CrshOverride Mar 25, 2020
0969835
Add yet another seemingly missing doc update
CrshOverride Mar 25, 2020
f1de9f1
Catch the error, decode the body, and re-throw
CrshOverride Apr 17, 2020
0b2da12
Remove the updates now that the change is non-breaking
CrshOverride Apr 17, 2020
661a621
Regenerate client
CrshOverride Apr 17, 2020
703e0ce
Add bytes deserialization test
CrshOverride Apr 17, 2020
85820b0
Update exception parsing
CrshOverride Apr 17, 2020
250d36e
Add exception parsing for python-experimental
CrshOverride Apr 17, 2020
31455af
Regenerate client with minor changes
CrshOverride Apr 17, 2020
d6a7f39
Revert test changes
CrshOverride Apr 17, 2020
141a9a3
Merge branch 'master' of github.com:OpenAPITools/openapi-generator
CrshOverride Apr 20, 2020
654f1cd
Regenerate model_utils.py
CrshOverride Apr 20, 2020
ce0fb7c
Update confusing test name
CrshOverride Apr 20, 2020
2f9e8f5
Remove bytes from mapping and examples
CrshOverride Apr 20, 2020
5fe3095
Merge branch 'master' of github.com:CrshOverride/openapi-generator
CrshOverride Apr 20, 2020
590986b
Add back in the old binary/ByteArray to str mapping
CrshOverride Apr 20, 2020
f260bac
Update docs and api_client template
CrshOverride Apr 21, 2020
a7ade74
Add experimental api_client changes
CrshOverride Apr 21, 2020
e9edfdf
Regenerate samples again
CrshOverride Apr 21, 2020
14b2612
Add Tornado handling to early return
CrshOverride Apr 21, 2020
127dfef
Try fixing Tornado python returns
CrshOverride Apr 21, 2020
60f9fc9
More documentation changes
CrshOverride Apr 21, 2020
663d947
Re-generate the client code
CrshOverride Apr 21, 2020
9af7a7b
Remove bytes from test_format_test
CrshOverride Apr 21, 2020
2d2a571
Remove more leftover bytes usages
CrshOverride Apr 21, 2020
927f87d
Switch bytes validation back to string
CrshOverride Apr 21, 2020
f65c8c5
Merge branch 'master' of github.com:CrshOverride/openapi-generator
CrshOverride Apr 21, 2020
206fe3c
Fix format_test template and regenerate
CrshOverride Apr 21, 2020
e55c9fa
Merge branch 'master' of github.com:CrshOverride/openapi-generator
CrshOverride Apr 21, 2020
9c8750f
Remove unused bytes var
CrshOverride Apr 21, 2020
e0d058e
Remove bytes import from models and regenerate
CrshOverride Apr 22, 2020
4d02d88
Remove bytes import from test_deserialization
CrshOverride Apr 22, 2020
91573d6
Reduce nested ifs
CrshOverride Apr 23, 2020
b9834fc
Remove byte logic for now
CrshOverride Apr 23, 2020
d67fe9f
Regenerate client after latest changes
CrshOverride Apr 23, 2020
c84d165
Remove another bytes usage
CrshOverride Apr 23, 2020
aa44de5
Regenerate after removing dangling byte string usage
CrshOverride Apr 23, 2020
5e8e54c
Reduce the scope of the try/catch in api_client
CrshOverride Apr 23, 2020
4175601
Regenerate after try/catch cleanup
CrshOverride Apr 24, 2020
01aed89
Swap catch for except
CrshOverride Apr 24, 2020
6a76167
Regenerate Python client after api_client change
CrshOverride Apr 24, 2020
8673b87
Fix lint error on the generated api_client
CrshOverride Apr 24, 2020
23ab4e1
Add binary format test back in w/ string
CrshOverride Apr 24, 2020
1de177c
Add decoding to python-experimental and regenerate
CrshOverride Apr 24, 2020
f806ca8
Import re into python-experimental api_client
CrshOverride Apr 24, 2020
373782f
Ensure file upload json response is utf-8 encoded bytes
CrshOverride Apr 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/python-experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ sidebar_label: python-experimental

<ul class="column-ul">
<li>bool</li>
<li>bytes</li>
<li>date</li>
<li>datetime</li>
<li>dict</li>
Expand Down
1 change: 1 addition & 0 deletions docs/generators/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ sidebar_label: python

<ul class="column-ul">
<li>bool</li>
<li>bytes</li>
<li>date</li>
<li>datetime</li>
<li>dict</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public PythonClientCodegen() {
languageSpecificPrimitives.add("object");
// TODO file and binary is mapped as `file`
languageSpecificPrimitives.add("file");
languageSpecificPrimitives.add("bytes");
spacether marked this conversation as resolved.
Show resolved Hide resolved

typeMapping.clear();
typeMapping.put("integer", "int");
Expand Down Expand Up @@ -828,7 +829,7 @@ private String toExampleValueRecursive(Schema schema, List<String> included_sche
if (schema.getDiscriminator()!=null) {
toExclude = schema.getDiscriminator().getPropertyName();
}

example = packageName + ".models." + underscore(schema.getTitle())+"."+schema.getTitle()+"(";

// if required only:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,8 +882,8 @@ public String getSimpleTypeDeclaration(Schema schema) {
* Return a string representation of the Python types for the specified schema.
* Primitive types in the OAS specification are implemented in Python using the corresponding
* Python primitive types.
* Composed types (e.g. allAll, oneOf, anyOf) are represented in Python using list of types.
*
* Composed types (e.g. allAll, oneOf, anyOf) are represented in Python using list of types.
*
* @param p The OAS schema.
* @param prefix prepended to the returned value.
* @param suffix appended to the returned value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import tornado.gen
from {{packageName}}.configuration import Configuration
import {{modelPackage}}
from {{packageName}} import rest
from {{packageName}}.exceptions import ApiValueError
from {{packageName}}.exceptions import ApiValueError, ApiException


class ApiClient(object):
Expand Down Expand Up @@ -186,22 +186,43 @@ class ApiClient(object):
# use server/host defined in path or operation instead
url = _host + resource_path

# perform request and return response
response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
try:
# perform request and return response
response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
except ApiException as e:
e.body = e.body.decode('utf-8') if six.PY3 else e.body
raise e

content_type = response_data.getheader('content-type')

self.last_response = response_data

return_data = response_data
if _preload_content:
# deserialize response data
if response_type:
return_data = self.deserialize(response_data, response_type)
else:
return_data = None

if not _preload_content:
{{^tornado}}
return return_data
{{/tornado}}
{{#tornado}}
raise tornado.gen.Return(return_data)
{{/tornado}}

if six.PY3 and response_type not in ["file", "bytes"]:
match = None
if content_type is not None:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_data.data = response_data.data.decode(encoding)

# deserialize response data
if response_type:
return_data = self.deserialize(response_data, response_type)
else:
return_data = None

{{^tornado}}
if _return_http_data_only:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class RESTClientObject(object):
r = await self.pool_manager.request(**args)
if _preload_content:

data = await r.text()
data = await r.read()
r = RESTResponse(r, data)

# log response body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import atexit
import mimetypes
from multiprocessing.pool import ThreadPool
import os
import re

# python 2 and python 3 compatibility library
import six
Expand All @@ -17,7 +18,7 @@ import tornado.gen

from {{packageName}} import rest
from {{packageName}}.configuration import Configuration
from {{packageName}}.exceptions import ApiValueError
from {{packageName}}.exceptions import ApiValueError, ApiException
from {{packageName}}.model_utils import (
ModelNormal,
ModelSimple,
Expand Down Expand Up @@ -176,26 +177,48 @@ class ApiClient(object):
# use server/host defined in path or operation instead
url = _host + resource_path

# perform request and return response
response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
try:
# perform request and return response
response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
except ApiException as e:
e.body = e.body.decode('utf-8') if six.PY3 else e.body
spacether marked this conversation as resolved.
Show resolved Hide resolved
raise e

content_type = response_data.getheader('content-type')

self.last_response = response_data

return_data = response_data
if _preload_content:
# deserialize response data
if response_type:
return_data = self.deserialize(
response_data,
response_type,
_check_type
)
else:
return_data = None

if not _preload_content:
{{^tornado}}
return (return_data)
{{/tornado}}
{{#tornado}}
raise tornado.gen.Return(return_data)
{{/tornado}}
return return_data
spacether marked this conversation as resolved.
Show resolved Hide resolved

if six.PY3 and response_type not in ["file", "bytes"]:
match = None
if content_type is not None:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_data.data = response_data.data.decode(encoding)

# deserialize response data
if response_type:
return_data = self.deserialize(
response_data,
response_type,
_check_type
)
else:
return_data = None

{{^tornado}}
if _return_http_data_only:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,6 @@ class RESTClientObject(object):
if _preload_content:
r = RESTResponse(r)

# In the python 3, the response.data is bytes.
# we need to decode it to string.
if six.PY3:
r.data = r.data.decode('utf8')

# log response body
logger.debug("response body: %s", r.data)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import logging
import re

# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import urlencode
import tornado
import tornado.gen
Expand All @@ -28,11 +27,7 @@ class RESTResponse(io.IOBase):
self.reason = resp.reason

if resp.body:
# In Python 3, the response body is utf-8 encoded bytes.
if six.PY3:
self.data = resp.body.decode('utf-8')
else:
self.data = resp.body
self.data = resp.body
else:
self.data = None

Expand Down
42 changes: 29 additions & 13 deletions samples/client/petstore/python-asyncio/petstore_api/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from petstore_api.configuration import Configuration
import petstore_api.models
from petstore_api import rest
from petstore_api.exceptions import ApiValueError
from petstore_api.exceptions import ApiValueError, ApiException


class ApiClient(object):
Expand Down Expand Up @@ -177,22 +177,38 @@ async def __call_api(
# use server/host defined in path or operation instead
url = _host + resource_path

# perform request and return response
response_data = await self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
try:
# perform request and return response
response_data = await self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
except ApiException as e:
e.body = e.body.decode('utf-8') if six.PY3 else e.body
raise e

content_type = response_data.getheader('content-type')

self.last_response = response_data

return_data = response_data
if _preload_content:
# deserialize response data
if response_type:
return_data = self.deserialize(response_data, response_type)
else:
return_data = None

if not _preload_content:
return return_data

if six.PY3 and response_type not in ["file", "bytes"]:
match = None
if content_type is not None:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_data.data = response_data.data.decode(encoding)

# deserialize response data
if response_type:
return_data = self.deserialize(response_data, response_type)
else:
return_data = None

if _return_http_data_only:
return (return_data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def request(self, method, url, query_params=None, headers=None,
r = await self.pool_manager.request(**args)
if _preload_content:

data = await r.text()
data = await r.read()
r = RESTResponse(r, data)

# log response body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
import mimetypes
from multiprocessing.pool import ThreadPool
import os
import re

# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import quote

from petstore_api import rest
from petstore_api.configuration import Configuration
from petstore_api.exceptions import ApiValueError
from petstore_api.exceptions import ApiValueError, ApiException
from petstore_api.model_utils import (
ModelNormal,
ModelSimple,
Expand Down Expand Up @@ -178,26 +179,43 @@ def __call_api(
# use server/host defined in path or operation instead
url = _host + resource_path

# perform request and return response
response_data = self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
try:
# perform request and return response
response_data = self.request(
method, url, query_params=query_params, headers=header_params,
post_params=post_params, body=body,
_preload_content=_preload_content,
_request_timeout=_request_timeout)
except ApiException as e:
e.body = e.body.decode('utf-8') if six.PY3 else e.body
raise e

content_type = response_data.getheader('content-type')

self.last_response = response_data

return_data = response_data
if _preload_content:
# deserialize response data
if response_type:
return_data = self.deserialize(
response_data,
response_type,
_check_type
)
else:
return_data = None

if not _preload_content:
return (return_data)
return return_data

if six.PY3 and response_type not in ["file", "bytes"]:
match = None
if content_type is not None:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_data.data = response_data.data.decode(encoding)

# deserialize response data
if response_type:
return_data = self.deserialize(
response_data,
response_type,
_check_type
)
else:
return_data = None

if _return_http_data_only:
return (return_data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,6 @@ def request(self, method, url, query_params=None, headers=None,
if _preload_content:
r = RESTResponse(r)

# In the python 3, the response.data is bytes.
# we need to decode it to string.
if six.PY3:
r.data = r.data.decode('utf8')

# log response body
logger.debug("response body: %s", r.data)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,4 @@ def test_string(self):


if __name__ == '__main__':
unittest.main()
unittest.main()
Loading