Skip to content

Commit

Permalink
Implement future.cancel(), fix type annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaensch committed Feb 20, 2019
1 parent 9ad344e commit af1b9b9
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 23 deletions.
15 changes: 14 additions & 1 deletion bravado_asyncio/future_adapter.py
@@ -1,14 +1,21 @@
import asyncio
import concurrent.futures
import time
from typing import Any
from typing import Optional

import aiohttp.client_exceptions
from bravado.http_future import FutureAdapter as BaseFutureAdapter
from bravado.http_future import FutureAdapter as BravadoFutureAdapter

from bravado_asyncio.definitions import AsyncioResponse


class BaseFutureAdapter(BravadoFutureAdapter):

def __init__(self, future: Any) -> None:
raise NotImplementedError('Do not instantiate BaseFutureAdapter, use one of its subclasses')


class FutureAdapter(BaseFutureAdapter):
"""FutureAdapter that will be used when run_mode is THREAD. The result method is
a normal Python function, and we expect future to be from the concurrent.futures module."""
Expand All @@ -27,6 +34,9 @@ def result(self, timeout: Optional[float] = None) -> AsyncioResponse:

return AsyncioResponse(response=response, remaining_timeout=remaining_timeout)

def cancel(self) -> None:
self.future.cancel()


class AsyncioFutureAdapter(BaseFutureAdapter):
"""FutureAdapter that will be used when run_mode is FULL_ASYNCIO. The result method is
Expand All @@ -45,3 +55,6 @@ async def result(self, timeout: Optional[float] = None) -> AsyncioResponse:
remaining_timeout = timeout - time_elapsed if timeout else None

return AsyncioResponse(response=response, remaining_timeout=remaining_timeout)

def cancel(self) -> None:
self.future.cancel()
8 changes: 5 additions & 3 deletions bravado_asyncio/http_client.py
Expand Up @@ -6,8 +6,10 @@
from typing import Callable # noqa: F401
from typing import cast
from typing import Dict
from typing import MutableMapping
from typing import Optional
from typing import Sequence
from typing import Type
from typing import Union

import aiohttp
Expand All @@ -23,12 +25,12 @@

from bravado_asyncio.definitions import RunMode
from bravado_asyncio.future_adapter import AsyncioFutureAdapter
from bravado_asyncio.future_adapter import BaseFutureAdapter
from bravado_asyncio.future_adapter import FutureAdapter
from bravado_asyncio.response_adapter import AioHTTPResponseAdapter
from bravado_asyncio.response_adapter import AsyncioHTTPResponseAdapter
from bravado_asyncio.thread_loop import get_thread_loop


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -83,7 +85,7 @@ def __init__(
self.run_coroutine_func = asyncio.run_coroutine_threadsafe # type: Callable
self.response_adapter = AioHTTPResponseAdapter
self.bravado_future_class = HttpFuture
self.future_adapter = FutureAdapter # type: http_future.FutureAdapter
self.future_adapter = FutureAdapter # type: Type[BaseFutureAdapter]
elif run_mode == RunMode.FULL_ASYNCIO:
from aiobravado.http_future import HttpFuture as AsyncioHttpFuture

Expand Down Expand Up @@ -119,7 +121,7 @@ def __init__(

def request(
self,
request_params: Dict[str, Any],
request_params: MutableMapping[str, Any],
operation: Optional[Operation] = None,
request_config: Optional[RequestConfig] = None,
) -> HttpFuture:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -31,7 +31,7 @@
],
install_requires=[
'aiohttp>=3.3',
'bravado>=10.0.0',
'bravado>=10.3.0',
'yelp-bytes',
],
extras_require={
Expand Down
3 changes: 0 additions & 3 deletions tests/integration/bravado_integration_test.py
Expand Up @@ -14,9 +14,6 @@ class TestServerBravadoAsyncioClient(IntegrationTestsBaseClass):
aiohttp.ClientConnectionError(),
}

def cancel_http_future(self, http_future):
http_future.future.future.cancel()

def test_bytes_header(self, swagger_http_server):
# TODO: integrate this test into bravado integration tests suite
response = self.http_client.request({
Expand Down
37 changes: 22 additions & 15 deletions tests/integration/integration_test.py
Expand Up @@ -2,6 +2,7 @@
import io
import os.path
import time
from concurrent.futures import CancelledError

import pytest
from bravado import requests_client
Expand All @@ -20,10 +21,14 @@ def swagger_client(integration_server, request):
# to make sure they both behave the same.
# Once this integration suite has become stable (i.e. we're happy with the approach and the test coverage)
# it could move to bravado and test all major HTTP clients (requests, fido, asyncio).
spec_url = '{}/swagger.yaml'.format(integration_server)
return get_swagger_client(integration_server, request.param())


def get_swagger_client(server_url, http_client_instance):
spec_url = '{}/swagger.yaml'.format(server_url)
return SwaggerClient.from_url(
spec_url,
http_client=request.param(),
http_client=http_client_instance,
config={'also_return_response': True},
)

Expand Down Expand Up @@ -155,6 +160,17 @@ def test_server_500(swagger_client):
swagger_client.pet.deletePet(petId=42).result(timeout=1)


def test_cancellation(integration_server):
swagger_client = get_swagger_client(integration_server, http_client.AsyncioClient())
bravado_future = swagger_client.store.getInventory() # request takes roughly 1 second to complete
bravado_future.cancel()

with pytest.raises(CancelledError):
bravado_future.result()

bravado_future.cancel() # make sure we can call it again without issues


def test_timeout_on_future(swagger_client):
with pytest.raises(BravadoTimeoutError):
bravado_future = swagger_client.store.getInventory()
Expand Down Expand Up @@ -203,20 +219,11 @@ async def sleep_coroutine():
return 42


async def get_swagger_client(spec_url):
return SwaggerClient.from_url(
spec_url,
http_client=http_client.AsyncioClient(),
)


async def _test_asyncio_client(integration_server):
spec_url = '{}/swagger.yaml'.format(integration_server)
# schedule our first coroutine (after _test_asyncio_client) in the default event loop
future = asyncio.ensure_future(sleep_coroutine())
# more work for the default event loop
client1 = await get_swagger_client(spec_url)
client2 = await get_swagger_client(spec_url.replace('localhost', '127.0.0.1'))
client1 = get_swagger_client(integration_server, http_client.AsyncioClient())
client2 = get_swagger_client(integration_server.replace('localhost', '127.0.0.1'), http_client.AsyncioClient())

# two tasks for the event loop running in a separate thread
future1 = client1.store.getInventory()
Expand All @@ -225,10 +232,10 @@ async def _test_asyncio_client(integration_server):
result = await future
assert result == 42

result1 = future1.result(timeout=5)
result1, _ = future1.result(timeout=5)
assert result1 == {}

result2 = future2.result(timeout=5)
result2, _ = future2.result(timeout=5)
assert result2 == {}

return True

0 comments on commit af1b9b9

Please sign in to comment.