Skip to content

Commit b08fb4e

Browse files
committed
Merge pull request #2472 from wing328/python_auto_doc
[Python] automatically generate documentation (markdown) for Python API client
2 parents e7b85f0 + c0f5bca commit b08fb4e

File tree

28 files changed

+2353
-96
lines changed

28 files changed

+2353
-96
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/PythonClientCodegen.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.swagger.codegen.CliOption;
44
import io.swagger.codegen.CodegenConfig;
55
import io.swagger.codegen.CodegenConstants;
6+
import io.swagger.codegen.CodegenParameter;
67
import io.swagger.codegen.CodegenType;
78
import io.swagger.codegen.DefaultCodegen;
89
import io.swagger.codegen.SupportingFile;
@@ -17,6 +18,8 @@
1718
public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig {
1819
protected String packageName;
1920
protected String packageVersion;
21+
protected String apiDocPath = "docs/";
22+
protected String modelDocPath = "docs/";
2023

2124
public PythonClientCodegen() {
2225
super();
@@ -28,6 +31,9 @@ public PythonClientCodegen() {
2831
apiTemplateFiles.put("api.mustache", ".py");
2932
embeddedTemplateDir = templateDir = "python";
3033

34+
modelDocTemplateFiles.put("model_doc.mustache", ".md");
35+
apiDocTemplateFiles.put("api_doc.mustache", ".md");
36+
3137
languageSpecificPrimitives.clear();
3238
languageSpecificPrimitives.add("int");
3339
languageSpecificPrimitives.add("float");
@@ -36,6 +42,7 @@ public PythonClientCodegen() {
3642
languageSpecificPrimitives.add("str");
3743
languageSpecificPrimitives.add("datetime");
3844
languageSpecificPrimitives.add("date");
45+
languageSpecificPrimitives.add("object");
3946

4047
typeMapping.clear();
4148
typeMapping.put("integer", "int");
@@ -97,6 +104,10 @@ public void processOpts() {
97104
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
98105
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
99106

107+
// make api and model doc path available in mustache template
108+
additionalProperties.put("apiDocPath", apiDocPath);
109+
additionalProperties.put("modelDocPath", modelDocPath);
110+
100111
String swaggerFolder = packageName;
101112

102113
modelPackage = swaggerFolder + File.separatorChar + "models";
@@ -138,6 +149,27 @@ public String escapeReservedWord(String name) {
138149
return "_" + name;
139150
}
140151

152+
@Override
153+
public String apiDocFileFolder() {
154+
return (outputFolder + "/" + apiDocPath);
155+
}
156+
157+
@Override
158+
public String modelDocFileFolder() {
159+
return (outputFolder + "/" + modelDocPath);
160+
}
161+
162+
@Override
163+
public String toModelDocFilename(String name) {
164+
return toModelName(name);
165+
}
166+
167+
@Override
168+
public String toApiDocFilename(String name) {
169+
return toApiName(name);
170+
}
171+
172+
141173
@Override
142174
public String apiFileFolder() {
143175
return outputFolder + File.separatorChar + apiPackage().replace('.', File.separatorChar);
@@ -388,4 +420,70 @@ public String toDefaultValue(Property p) {
388420
return null;
389421
}
390422

423+
@Override
424+
public void setParameterExampleValue(CodegenParameter p) {
425+
String example;
426+
427+
if (p.defaultValue == null) {
428+
example = p.example;
429+
} else {
430+
example = p.defaultValue;
431+
}
432+
433+
String type = p.baseType;
434+
if (type == null) {
435+
type = p.dataType;
436+
}
437+
438+
if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) {
439+
if (example == null) {
440+
example = p.paramName + "_example";
441+
}
442+
example = "'" + escapeText(example) + "'";
443+
} else if ("Integer".equals(type) || "int".equals(type)) {
444+
if (example == null) {
445+
example = "56";
446+
}
447+
} else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) {
448+
if (example == null) {
449+
example = "3.4";
450+
}
451+
} else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) {
452+
if (example == null) {
453+
example = "True";
454+
}
455+
} else if ("file".equalsIgnoreCase(type)) {
456+
if (example == null) {
457+
example = "/path/to/file";
458+
}
459+
example = "'" + escapeText(example) + "'";
460+
} else if ("Date".equalsIgnoreCase(type)) {
461+
if (example == null) {
462+
example = "2013-10-20";
463+
}
464+
example = "'" + escapeText(example) + "'";
465+
} else if ("DateTime".equalsIgnoreCase(type)) {
466+
if (example == null) {
467+
example = "2013-10-20T19:20:30+01:00";
468+
}
469+
example = "'" + escapeText(example) + "'";
470+
} else if (!languageSpecificPrimitives.contains(type)) {
471+
// type is a model class, e.g. User
472+
example = this.packageName + "." + type + "()";
473+
} else {
474+
LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue");
475+
}
476+
477+
if (example == null) {
478+
example = "NULL";
479+
} else if (Boolean.TRUE.equals(p.isListContainer)) {
480+
example = "[" + example + "]";
481+
} else if (Boolean.TRUE.equals(p.isMapContainer)) {
482+
example = "{'key': " + example + "}";
483+
}
484+
485+
p.example = example;
486+
}
487+
488+
391489
}
Lines changed: 95 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,122 @@
1+
# {{packageName}}
2+
{{#appDescription}}
3+
{{{appDescription}}}
4+
{{/appDescription}}
5+
6+
This Python package is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project:
7+
8+
- API version: {{appVersion}}
9+
- Package version: {{packageVersion}}
10+
- Build date: {{generatedDate}}
11+
- Build package: {{generatorClass}}
12+
{{#infoUrl}}
13+
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
14+
{{/infoUrl}}
15+
116
## Requirements.
2-
Python 2.7 and later.
317

4-
## Setuptools
5-
You can install the bindings via [Setuptools](http://pypi.python.org/pypi/setuptools).
18+
Python 2.7 and 3.4+
19+
20+
## Installation & Usage
21+
### pip install
22+
23+
If the python package is hosted on Github, you can install directly from Github
624

725
```sh
8-
python setup.py install
26+
pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git
927
```
28+
(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git`)
1029

11-
Or you can install from Github via pip:
30+
Then import the package:
31+
```python
32+
import {{{packageName}}}
33+
```
34+
35+
### Setuptools
36+
37+
Install via [Setuptools](http://pypi.python.org/pypi/setuptools).
1238

1339
```sh
14-
pip install git+https://github.com/geekerzp/swagger_client.git
40+
python setup.py install --user
1541
```
42+
(or `sudo python setup.py install` to install the package for all users)
1643

17-
To use the bindings, import the pacakge:
18-
44+
Then import the package:
1945
```python
20-
import swagger_client
46+
import {{{packageName}}}
2147
```
2248

23-
## Manual Installation
24-
If you do not wish to use setuptools, you can download the latest release.
25-
Then, to use the bindings, import the package:
49+
## Getting Started
50+
51+
Please follow the [installation procedure](#installation--usage) and then run the following:
2652

2753
```python
28-
import path.to.swagger_client
54+
import time
55+
import {{{packageName}}}
56+
from {{{packageName}}}.rest import ApiException
57+
from pprint import pprint
58+
{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}}{{#hasAuthMethods}}{{#authMethods}}{{#isBasic}}
59+
# Configure HTTP basic authorization: {{{name}}}
60+
{{{packageName}}}.configuration.username = 'YOUR_USERNAME'
61+
{{{packageName}}}.configuration.password = 'YOUR_PASSWORD'{{/isBasic}}{{#isApiKey}}
62+
# Configure API key authorization: {{{name}}}
63+
{{{packageName}}}.configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY'
64+
# Uncomment below to setup prefix (e.g. BEARER) for API key, if needed
65+
# {{{packageName}}}.configuration.api_key_prefix['{{{keyParamName}}}'] = 'BEARER'{{/isApiKey}}{{#isOAuth}}
66+
# Configure OAuth2 access token for authorization: {{{name}}}
67+
{{{packageName}}}.configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}}{{/authMethods}}
68+
{{/hasAuthMethods}}
69+
# create an instance of the API class
70+
api_instance = {{{packageName}}}.{{{classname}}}
71+
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
72+
{{/allParams}}
73+
74+
try:
75+
{{#summary}} # {{{.}}}
76+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
77+
pprint(api_response){{/returnType}}
78+
except ApiException as e:
79+
print "Exception when calling {{classname}}->{{operationId}}: %s\n" % e
80+
{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
2981
```
3082

31-
## Getting Started
83+
## Documentation for API Endpoints
3284

33-
TODO
85+
All URIs are relative to *{{basePath}}*
3486

35-
## Documentation
87+
Class | Method | HTTP request | Description
88+
------------ | ------------- | ------------- | -------------
89+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
90+
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
3691

37-
TODO
92+
## Documentation For Models
3893

39-
## Tests
94+
{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
95+
{{/model}}{{/models}}
4096

41-
(Please make sure you have [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) installed)
97+
## Documentation For Authorization
4298

43-
Execute the following command to run the tests in the current Python (v2 or v3) environment:
99+
{{^authMethods}} All endpoints do not require authorization.
100+
{{/authMethods}}{{#authMethods}}{{#last}} Authentication schemes defined for the API:{{/last}}{{/authMethods}}
101+
{{#authMethods}}## {{{name}}}
44102

45-
```sh
46-
$ make test
47-
[... magically installs dependencies and runs tests on your virtualenv]
48-
Ran 7 tests in 19.289s
103+
{{#isApiKey}}- **Type**: API key
104+
- **API key parameter name**: {{{keyParamName}}}
105+
- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
106+
{{/isApiKey}}
107+
{{#isBasic}}- **Type**: HTTP basic authentication
108+
{{/isBasic}}
109+
{{#isOAuth}}- **Type**: OAuth
110+
- **Flow**: {{{flow}}}
111+
- **Authorizatoin URL**: {{{authorizationUrl}}}
112+
- **Scopes**: {{^scopes}}N/A{{/scopes}}
113+
{{#scopes}} - **{{{scope}}}**: {{{description}}}
114+
{{/scopes}}
115+
{{/isOAuth}}
49116

50-
OK
51-
```
52-
or
117+
{{/authMethods}}
53118

54-
```
55-
$ mvn integration-test -rf :PythonPetstoreClientTests
56-
Using 2195432783 as seed
57-
[INFO] ------------------------------------------------------------------------
58-
[INFO] BUILD SUCCESS
59-
[INFO] ------------------------------------------------------------------------
60-
[INFO] Total time: 37.594 s
61-
[INFO] Finished at: 2015-05-16T18:00:35+08:00
62-
[INFO] Final Memory: 11M/156M
63-
[INFO] ------------------------------------------------------------------------
64-
```
65-
If you want to run the tests in all the python platforms:
119+
## Author
66120

67-
```sh
68-
$ make test-all
69-
[... tox creates a virtualenv for every platform and runs tests inside of each]
70-
py27: commands succeeded
71-
py34: commands succeeded
72-
congratulations :)
73-
```
121+
{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}}
122+
{{/hasMore}}{{/apis}}{{/apiInfo}}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class {{classname}}(object):
4747
self.api_client = config.api_client
4848
{{#operation}}
4949

50-
def {{nickname}}(self, {{#sortParamsByRequiredFlag}}{{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}{{/sortParamsByRequiredFlag}}**kwargs):
50+
def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}{{/sortParamsByRequiredFlag}}**kwargs):
5151
"""
5252
{{{summary}}}
5353
{{{notes}}}
@@ -59,10 +59,10 @@ class {{classname}}(object):
5959
>>> pprint(response)
6060
>>>
6161
{{#sortParamsByRequiredFlag}}
62-
>>> thread = api.{{nickname}}({{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}callback=callback_function)
62+
>>> thread = api.{{operationId}}({{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}callback=callback_function)
6363
{{/sortParamsByRequiredFlag}}
6464
{{^sortParamsByRequiredFlag}}
65-
>>> thread = api.{{nickname}}({{#allParams}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/allParams}}callback=callback_function)
65+
>>> thread = api.{{operationId}}({{#allParams}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/allParams}}callback=callback_function)
6666
{{/sortParamsByRequiredFlag}}
6767

6868
:param callback function: The callback function
@@ -83,7 +83,7 @@ class {{classname}}(object):
8383
if key not in all_params:
8484
raise TypeError(
8585
"Got an unexpected keyword argument '%s'"
86-
" to method {{nickname}}" % key
86+
" to method {{operationId}}" % key
8787
)
8888
params[key] = val
8989
del params['kwargs']
@@ -92,7 +92,7 @@ class {{classname}}(object):
9292
{{#required}}
9393
# verify the required parameter '{{paramName}}' is set
9494
if ('{{paramName}}' not in params) or (params['{{paramName}}'] is None):
95-
raise ValueError("Missing the required parameter `{{paramName}}` when calling `{{nickname}}`")
95+
raise ValueError("Missing the required parameter `{{paramName}}` when calling `{{operationId}}`")
9696
{{/required}}
9797
{{/allParams}}
9898

0 commit comments

Comments
 (0)