Skip to content

Commit 15345e1

Browse files
authored
[python] Cleanup ThreadPool with atexit rather than __del__ (#5094)
* [python] Cleanup ThreadPool with atexit rather than __del__ This removes the `__del__` function from the generated Python client, and replaces it with a `cleanup` function. When a ThreadPool is created, the cleanup function is registered with the `atexit` module. This fixes #5093, where the API client could hang indefinitely at garbage collection. * Update petstore examples * Test to ensure threadpool is cleaned up * Docs now encourage using the context manager * Regenerate docs * Update samples
1 parent d627282 commit 15345e1

58 files changed

Lines changed: 2968 additions & 2382 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

modules/openapi-generator/src/main/resources/python/api_client.mustache

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{{>partial_header}}
33
from __future__ import absolute_import
44

5+
import atexit
56
import datetime
67
from dateutil.parser import parse
78
import json
@@ -75,18 +76,27 @@ class ApiClient(object):
7576
self.user_agent = '{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}'
7677
self.client_side_validation = configuration.client_side_validation
7778

78-
def __del__(self):
79+
def __enter__(self):
80+
return self
81+
82+
def __exit__(self, exc_type, exc_value, traceback):
83+
self.close()
84+
85+
def close(self):
7986
if self._pool:
8087
self._pool.close()
8188
self._pool.join()
8289
self._pool = None
90+
if hasattr(atexit, 'unregister'):
91+
atexit.unregister(self.close)
8392

8493
@property
8594
def pool(self):
8695
"""Create thread pool on first request
8796
avoids instantiating unused threadpool for blocking clients.
8897
"""
8998
if self._pool is None:
99+
atexit.register(self.close)
90100
self._pool = ThreadPool(self.pool_threads)
91101
return self._pool
92102

modules/openapi-generator/src/main/resources/python/api_doc_example.mustache

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ from pprint import pprint
88
{{#hasAuthMethods}}
99
# Defining host is optional and default to {{{basePath}}}
1010
configuration.host = "{{{basePath}}}"
11-
# Create an instance of the API class
12-
api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration))
13-
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
14-
{{/allParams}}
11+
# Enter a context with an instance of the API client
12+
with {{{packageName}}}.ApiClient(configuration) as api_client:
13+
# Create an instance of the API class
14+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
15+
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
16+
{{/allParams}}
1517
{{/hasAuthMethods}}
1618
{{^hasAuthMethods}}
17-
# Create an instance of the API class
18-
api_instance = {{{packageName}}}.{{{classname}}}()
19-
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
20-
{{/allParams}}
19+
# Enter a context with an instance of the API client
20+
with {{{packageName}}}.ApiClient() as api_client:
21+
# Create an instance of the API class
22+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
23+
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
24+
{{/allParams}}
2125
{{/hasAuthMethods}}
2226

23-
try:
24-
{{#summary}} # {{{.}}}
25-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
26-
pprint(api_response){{/returnType}}
27-
except ApiException as e:
28-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
27+
try:
28+
{{#summary}} # {{{.}}}
29+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
30+
pprint(api_response){{/returnType}}
31+
except ApiException as e:
32+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
2933
```

modules/openapi-generator/src/main/resources/python/common_README.mustache

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ from pprint import pprint
88
{{> python_doc_auth_partial}}
99
# Defining host is optional and default to {{{basePath}}}
1010
configuration.host = "{{{basePath}}}"
11-
# Create an instance of the API class
12-
api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration))
13-
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
14-
{{/allParams}}
11+
# Enter a context with an instance of the API client
12+
with {{{packageName}}}.ApiClient(configuration) as api_client:
13+
# Create an instance of the API class
14+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
15+
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
16+
{{/allParams}}
1517

16-
try:
17-
{{#summary}} # {{{.}}}
18-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
19-
pprint(api_response){{/returnType}}
20-
except ApiException as e:
21-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
22-
{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
18+
try:
19+
{{#summary}} # {{{.}}}
20+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
21+
pprint(api_response){{/returnType}}
22+
except ApiException as e:
23+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
24+
{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
2325
```
2426

2527
## Documentation for API Endpoints

modules/openapi-generator/src/main/resources/python/python-experimental/README_common.mustache

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ from pprint import pprint
77
{{> python_doc_auth_partial}}
88
# Defining host is optional and default to {{{basePath}}}
99
configuration.host = "{{{basePath}}}"
10-
# Create an instance of the API class
11-
api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration))
12-
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
13-
{{/allParams}}
10+
# Enter a context with an instance of the API client
11+
with {{{packageName}}}.ApiClient(configuration) as api_client:
12+
# Create an instance of the API class
13+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
14+
{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
15+
{{/allParams}}
1416

15-
try:
16-
{{#summary}} # {{{.}}}
17-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
18-
pprint(api_response){{/returnType}}
19-
except {{{packageName}}}.ApiException as e:
20-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
21-
{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
17+
try:
18+
{{#summary}} # {{{.}}}
19+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
20+
pprint(api_response){{/returnType}}
21+
except {{{packageName}}}.ApiException as e:
22+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
23+
{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
2224
```
2325

2426
## Documentation for API Endpoints

modules/openapi-generator/src/main/resources/python/python-experimental/api_client.mustache

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import absolute_import
44

55
import json
6+
import atexit
67
import mimetypes
78
from multiprocessing.pool import ThreadPool
89
import os
@@ -74,18 +75,27 @@ class ApiClient(object):
7475
# Set default User-Agent.
7576
self.user_agent = '{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}'
7677

77-
def __del__(self):
78+
def __enter__(self):
79+
return self
80+
81+
def __exit__(self, exc_type, exc_value, traceback):
82+
self.close()
83+
84+
def close(self):
7885
if self._pool:
7986
self._pool.close()
8087
self._pool.join()
8188
self._pool = None
89+
if hasattr(atexit, 'unregister'):
90+
atexit.unregister(self.close)
8291

8392
@property
8493
def pool(self):
8594
"""Create thread pool on first request
8695
avoids instantiating unused threadpool for blocking clients.
8796
"""
8897
if self._pool is None:
98+
atexit.register(self.close)
8999
self._pool = ThreadPool(self.pool_threads)
90100
return self._pool
91101

modules/openapi-generator/src/main/resources/python/python-experimental/api_doc_example.mustache

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,55 @@ from pprint import pprint
77
{{#hasAuthMethods}}
88
# Defining host is optional and default to {{{basePath}}}
99
configuration.host = "{{{basePath}}}"
10-
# Create an instance of the API class
11-
api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration))
10+
# Enter a context with an instance of the API client
11+
with {{{packageName}}}.ApiClient(configuration) as api_client:
12+
# Create an instance of the API class
13+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
1214
{{/hasAuthMethods}}
1315
{{^hasAuthMethods}}
14-
# Create an instance of the API class
15-
api_instance = {{{packageName}}}.{{{classname}}}()
16+
# Enter a context with an instance of the API client
17+
with {{{packageName}}}.ApiClient(configuration) as api_client:
18+
# Create an instance of the API class
19+
api_instance = {{{packageName}}}.{{{classname}}}(api_client)
1620
{{/hasAuthMethods}}
17-
{{#requiredParams}}{{^defaultValue}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}
18-
{{/defaultValue}}{{/requiredParams}}{{#optionalParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}}
19-
{{/optionalParams}}
20-
{{#requiredParams}}
21-
{{^hasMore}}
21+
{{#requiredParams}}{{^defaultValue}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}
22+
{{/defaultValue}}{{/requiredParams}}{{#optionalParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}}
23+
{{/optionalParams}}
24+
{{#requiredParams}}
25+
{{^hasMore}}
2226

23-
# example passing only required values which don't have defaults set
24-
try:
25-
{{#summary}} # {{{.}}}
26-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/defaultValue}}{{/requiredParams}}){{#returnType}}
27-
pprint(api_response){{/returnType}}
28-
except {{{packageName}}}.ApiException as e:
29-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
30-
{{/hasMore}}
31-
{{/requiredParams}}
32-
{{#optionalParams}}
33-
{{^hasMore}}
27+
# example passing only required values which don't have defaults set
28+
try:
29+
{{#summary}} # {{{.}}}
30+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/defaultValue}}{{/requiredParams}}){{#returnType}}
31+
pprint(api_response){{/returnType}}
32+
except {{{packageName}}}.ApiException as e:
33+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
34+
{{/hasMore}}
35+
{{/requiredParams}}
36+
{{#optionalParams}}
37+
{{^hasMore}}
3438

35-
# example passing only required values which don't have defaults set
36-
# and optional values
37-
try:
38-
{{#summary}} # {{{.}}}
39-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}, {{/defaultValue}}{{/requiredParams}}{{#optionalParams}}{{paramName}}={{paramName}}{{#hasMore}}, {{/hasMore}}{{/optionalParams}}){{#returnType}}
40-
pprint(api_response){{/returnType}}
41-
except {{{packageName}}}.ApiException as e:
42-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
43-
{{/hasMore}}
44-
{{/optionalParams}}
45-
{{^requiredParams}}
46-
{{^optionalParams}}
39+
# example passing only required values which don't have defaults set
40+
# and optional values
41+
try:
42+
{{#summary}} # {{{.}}}
43+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}, {{/defaultValue}}{{/requiredParams}}{{#optionalParams}}{{paramName}}={{paramName}}{{#hasMore}}, {{/hasMore}}{{/optionalParams}}){{#returnType}}
44+
pprint(api_response){{/returnType}}
45+
except {{{packageName}}}.ApiException as e:
46+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
47+
{{/hasMore}}
48+
{{/optionalParams}}
49+
{{^requiredParams}}
50+
{{^optionalParams}}
4751

48-
# example, this endpoint has no required or optional parameters
49-
try:
50-
{{#summary}} # {{{.}}}
51-
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}(){{#returnType}}
52-
pprint(api_response){{/returnType}}
53-
except {{{packageName}}}.ApiException as e:
54-
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
55-
{{/optionalParams}}
56-
{{/requiredParams}}
52+
# example, this endpoint has no required or optional parameters
53+
try:
54+
{{#summary}} # {{{.}}}
55+
{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}(){{#returnType}}
56+
pprint(api_response){{/returnType}}
57+
except {{{packageName}}}.ApiException as e:
58+
print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e)
59+
{{/optionalParams}}
60+
{{/requiredParams}}
5761
```

samples/client/petstore/python-asyncio/README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,19 @@ from pprint import pprint
5454

5555
# Defining host is optional and default to http://petstore.swagger.io:80/v2
5656
configuration.host = "http://petstore.swagger.io:80/v2"
57-
# Create an instance of the API class
58-
api_instance = petstore_api.AnotherFakeApi(petstore_api.ApiClient(configuration))
59-
body = petstore_api.Client() # Client | client model
60-
61-
try:
62-
# To test special tags
63-
api_response = api_instance.call_123_test_special_tags(body)
64-
pprint(api_response)
65-
except ApiException as e:
66-
print("Exception when calling AnotherFakeApi->call_123_test_special_tags: %s\n" % e)
67-
57+
# Enter a context with an instance of the API client
58+
with petstore_api.ApiClient(configuration) as api_client:
59+
# Create an instance of the API class
60+
api_instance = petstore_api.AnotherFakeApi(api_client)
61+
body = petstore_api.Client() # Client | client model
62+
63+
try:
64+
# To test special tags
65+
api_response = api_instance.call_123_test_special_tags(body)
66+
pprint(api_response)
67+
except ApiException as e:
68+
print("Exception when calling AnotherFakeApi->call_123_test_special_tags: %s\n" % e)
69+
6870
```
6971

7072
## Documentation for API Endpoints

samples/client/petstore/python-asyncio/docs/AnotherFakeApi.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ import petstore_api
2323
from petstore_api.rest import ApiException
2424
from pprint import pprint
2525

26-
# Create an instance of the API class
27-
api_instance = petstore_api.AnotherFakeApi()
28-
body = petstore_api.Client() # Client | client model
29-
30-
try:
31-
# To test special tags
32-
api_response = api_instance.call_123_test_special_tags(body)
33-
pprint(api_response)
34-
except ApiException as e:
35-
print("Exception when calling AnotherFakeApi->call_123_test_special_tags: %s\n" % e)
26+
# Enter a context with an instance of the API client
27+
with petstore_api.ApiClient() as api_client:
28+
# Create an instance of the API class
29+
api_instance = petstore_api.AnotherFakeApi(api_client)
30+
body = petstore_api.Client() # Client | client model
31+
32+
try:
33+
# To test special tags
34+
api_response = api_instance.call_123_test_special_tags(body)
35+
pprint(api_response)
36+
except ApiException as e:
37+
print("Exception when calling AnotherFakeApi->call_123_test_special_tags: %s\n" % e)
3638
```
3739

3840
### Parameters

0 commit comments

Comments
 (0)