Skip to content

Commit d2cb05f

Browse files
committed
Merge pull request #797 from geekerzp/develop_2.0_python_auth
[Python] Add authentication support (API key, HTTP basic)
2 parents 3fe2169 + eb90b90 commit d2cb05f

File tree

20 files changed

+725
-272
lines changed

20 files changed

+725
-272
lines changed

modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/languages/PythonClientCodegen.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@ public PythonClientCodegen() {
2727
super();
2828

2929
eggPackage = module + "-python";
30-
invokerPackage = eggPackage + "/" + module;
30+
31+
invokerPackage = eggPackage + File.separatorChar + module;
3132

32-
outputFolder = "generated-code/python";
33+
outputFolder = "generated-code" + File.separatorChar + "python";
3334
modelTemplateFiles.put("model.mustache", ".py");
3435
apiTemplateFiles.put("api.mustache", ".py");
3536
templateDir = "python";
3637

37-
apiPackage = invokerPackage + ".apis";
38-
modelPackage = invokerPackage + ".models";
38+
apiPackage = invokerPackage + File.separatorChar + "apis";
39+
modelPackage = invokerPackage + File.separatorChar + "models";
3940

4041
languageSpecificPrimitives.clear();
4142
languageSpecificPrimitives.add("int");
@@ -68,13 +69,12 @@ public PythonClientCodegen() {
6869

6970
supportingFiles.add(new SupportingFile("README.mustache", eggPackage, "README.md"));
7071
supportingFiles.add(new SupportingFile("setup.mustache", eggPackage, "setup.py"));
71-
supportingFiles.add(new SupportingFile("swagger.mustache", invokerPackage, "swagger.py"));
72+
supportingFiles.add(new SupportingFile("api_client.mustache", invokerPackage, "api_client.py"));
7273
supportingFiles.add(new SupportingFile("rest.mustache", invokerPackage, "rest.py"));
73-
supportingFiles.add(new SupportingFile("util.mustache", invokerPackage, "util.py"));
74-
supportingFiles.add(new SupportingFile("config.mustache", invokerPackage, "config.py"));
74+
supportingFiles.add(new SupportingFile("configuration.mustache", invokerPackage, "configuration.py"));
7575
supportingFiles.add(new SupportingFile("__init__package.mustache", invokerPackage, "__init__.py"));
76-
supportingFiles.add(new SupportingFile("__init__model.mustache", modelPackage.replace('.', File.separatorChar), "__init__.py"));
77-
supportingFiles.add(new SupportingFile("__init__api.mustache", apiPackage.replace('.', File.separatorChar), "__init__.py"));
76+
supportingFiles.add(new SupportingFile("__init__model.mustache", modelPackage, "__init__.py"));
77+
supportingFiles.add(new SupportingFile("__init__api.mustache", apiPackage, "__init__.py"));
7878
}
7979

8080
@Override
@@ -84,11 +84,11 @@ public String escapeReservedWord(String name) {
8484

8585
@Override
8686
public String apiFileFolder() {
87-
return outputFolder + "/" + apiPackage().replace('.', File.separatorChar);
87+
return outputFolder + File.separatorChar + apiPackage().replace('.', File.separatorChar);
8888
}
8989

9090
public String modelFileFolder() {
91-
return outputFolder + "/" + modelPackage().replace('.', File.separatorChar);
91+
return outputFolder + File.separatorChar + modelPackage().replace('.', File.separatorChar);
9292
}
9393

9494
@Override

modules/swagger-codegen/src/main/resources/python/__init__package.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ from __future__ import absolute_import
77
{{#apiInfo}}{{#apis}}from .apis.{{classVarName}} import {{classname}}
88
{{/apis}}{{/apiInfo}}
99
# import ApiClient
10-
from .swagger import ApiClient
10+
from .api_client import ApiClient

modules/swagger-codegen/src/main/resources/python/api.mustache

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ import os
2727
# python 2 and python 3 compatibility library
2828
from six import iteritems
2929

30-
from ..util import remove_none
31-
32-
from .. import config
30+
from .. import configuration
31+
from ..api_client import ApiClient
3332

3433
{{#operations}}
3534
class {{classname}}(object):
@@ -38,7 +37,10 @@ class {{classname}}(object):
3837
if api_client:
3938
self.api_client = api_client
4039
else:
41-
self.api_client = config.api_client
40+
if not configuration.api_client:
41+
configuration.api_client = ApiClient('{{basePath}}')
42+
self.api_client = configuration.api_client
43+
4244
{{#operation}}
4345
def {{nickname}}(self, {{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}**kwargs):
4446
"""
@@ -66,13 +68,32 @@ class {{classname}}(object):
6668
resource_path = '{{path}}'.replace('{format}', 'json')
6769
method = '{{httpMethod}}'
6870

69-
path_params = remove_none(dict({{#pathParams}}{{baseName}}=params.get('{{paramName}}'){{#hasMore}}, {{/hasMore}}{{/pathParams}}))
70-
query_params = remove_none(dict({{#queryParams}}{{baseName}}=params.get('{{paramName}}'){{#hasMore}}, {{/hasMore}}{{/queryParams}}))
71-
header_params = remove_none(dict({{#headerParams}}{{baseName}}=params.get('{{paramName}}'){{#hasMore}}, {{/hasMore}}{{/headerParams}}))
72-
form_params = remove_none(dict({{#formParams}}{{^isFile}}{{baseName}}=params.get('{{paramName}}'){{#hasMore}}, {{/hasMore}}{{/isFile}}{{/formParams}}))
73-
files = remove_none(dict({{#formParams}}{{#isFile}}{{baseName}}=params.get('{{paramName}}'){{#hasMore}}, {{/hasMore}}{{/isFile}}{{/formParams}}))
74-
body_params = {{#bodyParam}}params.get('{{paramName}}'){{/bodyParam}}{{^bodyParam}}None{{/bodyParam}}
75-
71+
path_params = {}
72+
{{#pathParams}}
73+
if '{{paramName}}' in params:
74+
path_params['{{baseName}}'] = params['{{paramName}}']
75+
{{/pathParams}}
76+
query_params = {}
77+
{{#queryParams}}
78+
if '{{paramName}}' in params:
79+
query_params['{{baseName}}'] = params['{{paramName}}']
80+
{{/queryParams}}
81+
header_params = {}
82+
{{#headerParams}}
83+
if '{{paramName}}' in params:
84+
header_params['{{baseName}}'] = params['{{paramName}}']
85+
{{/headerParams}}
86+
form_params = {}
87+
files = {}
88+
{{#formParams}}
89+
if '{{paramName}}' in params:
90+
{{#notFile}}form_params['{{baseName}}'] = params['{{paramName}}']{{/notFile}}{{#isFile}}files['{{baseName}}'] = params['{{paramName}}']{{/isFile}}
91+
{{/formParams}}
92+
body_params = None
93+
{{#bodyParam}}
94+
if '{{paramName}}' in params:
95+
body_params = params['{{paramName}}']
96+
{{/bodyParam}}
7697
# HTTP header `Accept`
7798
header_params['Accept'] = self.api_client.select_header_accept([{{#produces}}'{{mediaType}}'{{#hasMore}}, {{/hasMore}}{{/produces}}])
7899
if not header_params['Accept']:
@@ -81,9 +102,12 @@ class {{classname}}(object):
81102
# HTTP header `Content-Type`
82103
header_params['Content-Type'] = self.api_client.select_header_content_type([{{#consumes}}'{{mediaType}}'{{#hasMore}}, {{/hasMore}}{{/consumes}}])
83104

105+
# Authentication setting
106+
auth_settings = [{{#authMethods}}'{{name}}'{{#hasMore}}, {{/hasMore}}{{/authMethods}}]
107+
84108
response = self.api_client.call_api(resource_path, method, path_params, query_params, header_params,
85109
body=body_params, post_params=form_params, files=files,
86-
response={{#returnType}}'{{returnType}}'{{/returnType}}{{^returnType}}None{{/returnType}})
110+
response={{#returnType}}'{{returnType}}'{{/returnType}}{{^returnType}}None{{/returnType}}, auth_settings=auth_settings)
87111
{{#returnType}}
88112
return response
89113
{{/returnType}}{{/operation}}
Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
# for python2
2929
from urllib import quote
3030

31+
from . import configuration
3132

3233
class ApiClient(object):
3334
"""
@@ -37,7 +38,7 @@ class ApiClient(object):
3738
:param header_name: a header to pass when making calls to the API
3839
:param header_value: a header value to pass when making calls to the API
3940
"""
40-
def __init__(self, host=None, header_name=None, header_value=None):
41+
def __init__(self, host=configuration.host, header_name=None, header_value=None):
4142
self.default_headers = {}
4243
if header_name is not None:
4344
self.default_headers[header_name] = header_value
@@ -58,15 +59,15 @@ def set_default_header(self, header_name, header_value):
5859
self.default_headers[header_name] = header_value
5960

6061
def call_api(self, resource_path, method, path_params=None, query_params=None, header_params=None,
61-
body=None, post_params=None, files=None, response=None):
62+
body=None, post_params=None, files=None, response=None, auth_settings=None):
6263

6364
# headers parameters
64-
headers = self.default_headers.copy()
65-
headers.update(header_params)
65+
header_params = header_params or {}
66+
header_params.update(self.default_headers)
6667
if self.cookie:
67-
headers['Cookie'] = self.cookie
68-
if headers:
69-
headers = self.sanitize_for_serialization(headers)
68+
header_params['Cookie'] = self.cookie
69+
if header_params:
70+
header_params = self.sanitize_for_serialization(header_params)
7071

7172
# path parameters
7273
if path_params:
@@ -85,6 +86,9 @@ def call_api(self, resource_path, method, path_params=None, query_params=None, h
8586
post_params = self.prepare_post_parameters(post_params, files)
8687
post_params = self.sanitize_for_serialization(post_params)
8788

89+
# auth setting
90+
self.update_params_for_auth(header_params, query_params, auth_settings)
91+
8892
# body
8993
if body:
9094
body = self.sanitize_for_serialization(body)
@@ -93,7 +97,7 @@ def call_api(self, resource_path, method, path_params=None, query_params=None, h
9397
url = self.host + resource_path
9498

9599
# perform request and return response
96-
response_data = self.request(method, url, query_params=query_params, headers=headers,
100+
response_data = self.request(method, url, query_params=query_params, headers=header_params,
97101
post_params=post_params, body=body)
98102

99103
# deserialize response data
@@ -244,11 +248,12 @@ def prepare_post_parameters(self, post_params=None, files=None):
244248

245249
if files:
246250
for k, v in iteritems(files):
247-
with open(v, 'rb') as f:
248-
filename = os.path.basename(f.name)
249-
filedata = f.read()
250-
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
251-
params[k] = tuple([filename, filedata, mimetype])
251+
if v:
252+
with open(v, 'rb') as f:
253+
filename = os.path.basename(f.name)
254+
filedata = f.read()
255+
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
256+
params[k] = tuple([filename, filedata, mimetype])
252257

253258
return params
254259

@@ -279,3 +284,20 @@ def select_header_content_type(self, content_types):
279284
return 'application/json'
280285
else:
281286
return content_types[0]
287+
288+
def update_params_for_auth(self, headers, querys, auth_settings):
289+
"""
290+
Update header and query params based on authentication setting
291+
"""
292+
if not auth_settings:
293+
return
294+
295+
for auth in auth_settings:
296+
auth_setting = configuration.auth_settings().get(auth)
297+
if auth_setting:
298+
if auth_setting['in'] == 'header':
299+
headers[auth_setting['key']] = auth_setting['value']
300+
elif auth_setting['in'] == 'query':
301+
querys[auth_setting['key']] = auth_setting['value']
302+
else:
303+
raise ValueError('Authentication token must be in `query` or `header`')

modules/swagger-codegen/src/main/resources/python/config.mustache

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from __future__ import absolute_import
2+
import base64
3+
import urllib3
4+
5+
def get_api_key_with_prefix(key):
6+
global api_key
7+
global api_key_prefix
8+
9+
if api_key.get(key) and api_key_prefix.get(key):
10+
return api_key_prefix[key] + ' ' + api_key[key]
11+
elif api_key.get(key):
12+
return api_key[key]
13+
14+
def get_basic_auth_token():
15+
global username
16+
global password
17+
18+
return urllib3.util.make_headers(basic_auth=username + ':' + password).get('authorization')
19+
20+
def auth_settings():
21+
return { {{#authMethods}}{{#isApiKey}}
22+
'{{name}}': {
23+
'type': 'api_key',
24+
'in': {{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}},
25+
'key': '{{keyParamName}}',
26+
'value': get_api_key_with_prefix('{{keyParamName}}')
27+
},
28+
{{/isApiKey}}{{#isBasic}}
29+
'{{name}}': {
30+
'type': 'basic',
31+
'in': 'header',
32+
'key': 'Authorization',
33+
'value': get_basic_auth_token()
34+
},
35+
{{/isBasic}}{{/authMethods}}
36+
}
37+
38+
# Default Base url
39+
host = "{{basePath}}"
40+
41+
# Default api client
42+
api_client = None
43+
44+
# Authentication settings
45+
46+
api_key = {}
47+
api_key_prefix = {}
48+
username = ''
49+
password = ''
50+
51+

modules/swagger-codegen/src/main/resources/python/rest.mustache

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# coding: utf-8
2+
3+
"""
4+
Credit: this file (rest.py) is modified based on rest.py in Dropbox Python SDK:
5+
https://www.dropbox.com/developers/core/sdks/python
6+
"""
7+
28
import sys
39
import io
410
import json
@@ -120,7 +126,7 @@ class RESTClientObject(object):
120126
r = RESTResponse(r)
121127

122128
if r.status not in range(200, 206):
123-
raise ErrorResponse(r)
129+
raise ApiException(r)
124130

125131
return self.process_response(r)
126132

@@ -157,7 +163,7 @@ class RESTClientObject(object):
157163
return self.request("PATCH", url, headers=headers, post_params=post_params, body=body)
158164

159165

160-
class ErrorResponse(Exception):
166+
class ApiException(Exception):
161167
"""
162168
Non-2xx HTTP response
163169
"""
@@ -184,7 +190,10 @@ class ErrorResponse(Exception):
184190
"""
185191
Custom error response messages
186192
"""
187-
return "({0})\nReason: {1}\nHeader: {2}\nBody: {3}\n".\
193+
return "({0})\n"\
194+
"Reason: {1}\n"\
195+
"HTTP response headers: {2}\n"\
196+
"HTTP response body: {3}\n".\
188197
format(self.status, self.reason, self.headers, self.body)
189198

190199
class RESTClient(object):

modules/swagger-codegen/src/main/resources/python/util.mustache

Lines changed: 0 additions & 17 deletions
This file was deleted.

samples/client/petstore/python/SwaggerPetstore-python/SwaggerPetstore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
from .apis.store_api import StoreApi
1414

1515
# import ApiClient
16-
from .swagger import ApiClient
16+
from .api_client import ApiClient

0 commit comments

Comments
 (0)