Bug Report Checklist
Description
When calling a generated API, the keyword parameter async_req=True is supposed to cause the request to be performed asynchronously, i.e. using multiprocessing.apply_async to handle __call_api instead of directly calling it.
However, in the --library asyncio target, __call_api is a coroutine, which multiprocessing.apply_async does not know how to handle.
openapi-generator version
4.3.0-SNAPSHOT, pulled as follows:
PS C:\Users\paulh> docker pull openapitools/openapi-generator-cli
Using default tag: latest
latest: Pulling from openapitools/openapi-generator-cli
709515475419: Already exists
38a1c0aaa6fd: Already exists
cd134db5e982: Already exists
044751432162: Pull complete
d41fd233bfb5: Pull complete
b9e8842b3734: Pull complete
Digest: sha256:76b6fc8b94789382e1d91319b787545ec5b7f29c3709c2c14bc47eab68d3a871
Status: Downloaded newer image for openapitools/openapi-generator-cli:latest
docker.io/openapitools/openapi-generator-cli:latest
PS C:\Users\paulh> docker image ls openapitools/openapi-generator-cli
REPOSITORY TAG IMAGE ID CREATED SIZE
openapitools/openapi-generator-cli latest e74d7eb8269d 29 minutes ago 135MB
I don't believe this is a recent regression, the code hasn't been touched since it was introduced in #107 in 2018.
OpenAPI declaration file content or url
https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml
Command line used for generation
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python --library asyncio -o /local/out/python
Steps to reproduce
After code-gen, create and run the following Python script (which includes workaround for #5538)
import asyncio
import sys
sys.path.append("out/python")
import openapi_client
from openapi_client.rest import ApiException
async def main():
configuration = openapi_client.Configuration()
with openapi_client.ApiClient(configuration) as api_client:
api_instance = openapi_client.PetApi(api_client)
try:
# Trigger a 404 search result
result = api_instance.get_pet_by_id("nosuchpet", async_req=True)
result.wait()
except ApiException as e:
print(f"get_pet_by_id failed: {e.reason}")
assert e.status == 404
raise e
finally:
await api_client.rest_client.pool_manager.close()
def monkey():
from openapi_client.rest import RESTClientObject
del RESTClientObject.__del__
monkey()
if __name__ == "__main__":
try:
asyncio.run(main())
except ApiException as e:
assert e.status == 404
pass
Actual output
C:\Program Files\Python37\lib\asyncio\events.py:88: RuntimeWarning: coroutine 'ApiClient.__call_api' was never awaited
self._context.run(self._callback, *self._args)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Notably, the request was never sent.
Expected output
get_pet_by_id failed: Not Found
Related issues/PRs
None I'm aware of.
Suggest a fix
Simple fix is to reject the async_req keyword in asyncio builds, since we're already "async", even though it's not threaded.
A more complex fix would be to have the asyncio target use concurrent.futures.ThreadPoolExecutor as the Pool, and asyncio.loop.run_in_executor instead of multiprocessing.apply_async to run the request in the pool.
That will then return an awaitable, which means the API for the caller is the same whether async_req is True or False, avoiding the ApplyResult needed in the other library's use of async_req.
Note that
result = api_instance.get_pet_by_id("nosuchpet", async_req=True)
await result.get()
works correctly, but it has actually performed the HTTP request on the main thread, and so all it did in the thread pool was create a coroutine object. So it's equivalent to async_req=False, but more wasteful.
So the open question is, does async_req indicate "threaded" or "async"?
Bug Report Checklist
Have you validated the input using an OpenAPI validator (example)?[Optional] Bounty to sponsor the fixDescription
When calling a generated API, the keyword parameter
async_req=Trueis supposed to cause the request to be performed asynchronously, i.e. usingmultiprocessing.apply_asyncto handle__call_apiinstead of directly calling it.However, in the
--library asynciotarget,__call_apiis a coroutine, whichmultiprocessing.apply_asyncdoes not know how to handle.openapi-generator version
4.3.0-SNAPSHOT, pulled as follows:
I don't believe this is a recent regression, the code hasn't been touched since it was introduced in #107 in 2018.
OpenAPI declaration file content or url
https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml
Command line used for generation
Steps to reproduce
After code-gen, create and run the following Python script (which includes workaround for #5538)
Actual output
Notably, the request was never sent.
Expected output
Related issues/PRs
None I'm aware of.
Suggest a fix
Simple fix is to reject the
async_reqkeyword in asyncio builds, since we're already "async", even though it's not threaded.A more complex fix would be to have the asyncio target use
concurrent.futures.ThreadPoolExecutoras the Pool, andasyncio.loop.run_in_executorinstead ofmultiprocessing.apply_asyncto run the request in the pool.That will then return an awaitable, which means the API for the caller is the same whether
async_reqisTrueorFalse, avoiding theApplyResultneeded in the other library's use ofasync_req.Note that
works correctly, but it has actually performed the HTTP request on the main thread, and so all it did in the thread pool was create a coroutine object. So it's equivalent to
async_req=False, but more wasteful.So the open question is, does
async_reqindicate "threaded" or "async"?