async def test_aiter_chunk(iter_client): resp = await iter_client.select_all(measurement='cpu_load', chunked=True, chunk_size=10, wrap=True) assert isasyncgen(resp.gen) chunks = [] async for chunk in resp.iterchunks(): chunks.append(chunk) logger.info(resp) logger.info(chunks[0]) assert len(chunks) == 10
def coroutine_or_error(async_fn, *args): def _return_value_looks_like_wrong_library(value): # Returned by legacy @asyncio.coroutine functions, which includes # a surprising proportion of asyncio builtins. if isinstance(value, collections.abc.Generator): return True # The protocol for detecting an asyncio Future-like object if getattr(value, "_asyncio_future_blocking", None) is not None: return True # This janky check catches tornado Futures and twisted Deferreds. # By the time we're calling this function, we already know # something has gone wrong, so a heuristic is pretty safe. if value.__class__.__name__ in ("Future", "Deferred"): return True return False try: coro = async_fn(*args) except TypeError: # Give good error for: nursery.start_soon(trio.sleep(1)) if isinstance(async_fn, collections.abc.Coroutine): # explicitly close coroutine to avoid RuntimeWarning async_fn.close() raise TypeError( "Trio was expecting an async function, but instead it got " "a coroutine object {async_fn!r}\n" "\n" "Probably you did something like:\n" "\n" " trio.run({async_fn.__name__}(...)) # incorrect!\n" " nursery.start_soon({async_fn.__name__}(...)) # incorrect!\n" "\n" "Instead, you want (notice the parentheses!):\n" "\n" " trio.run({async_fn.__name__}, ...) # correct!\n" " nursery.start_soon({async_fn.__name__}, ...) # correct!". format(async_fn=async_fn)) from None # Give good error for: nursery.start_soon(future) if _return_value_looks_like_wrong_library(async_fn): raise TypeError( "Trio was expecting an async function, but instead it got " "{!r} – are you trying to use a library written for " "asyncio/twisted/tornado or similar? That won't work " "without some sort of compatibility shim.".format( async_fn)) from None raise # We can't check iscoroutinefunction(async_fn), because that will fail # for things like functools.partial objects wrapping an async # function. So we have to just call it and then check whether the # return value is a coroutine object. if not isinstance(coro, collections.abc.Coroutine): # Give good error for: nursery.start_soon(func_returning_future) if _return_value_looks_like_wrong_library(coro): raise TypeError( "Trio got unexpected {!r} – are you trying to use a " "library written for asyncio/twisted/tornado or similar? " "That won't work without some sort of compatibility shim.". format(coro)) if isasyncgen(coro): raise TypeError( "start_soon expected an async function but got an async " "generator {!r}".format(coro)) # Give good error for: nursery.start_soon(some_sync_fn) raise TypeError( "Trio expected an async function, but {!r} appears to be " "synchronous".format(getattr(async_fn, "__qualname__", async_fn))) return coro
async def run(self, test_ctx, contextvars_ctx): __tracebackhide__ = True # This is a gross hack. I guess Trio should provide a context= # argument to start_soon/start? task = trio.hazmat.current_task() assert canary not in task.context task.context = contextvars_ctx # Force a yield so we pick up the new context await trio.sleep(0) # Check that it worked, since technically trio doesn't *guarantee* # that sleep(0) will actually yield. assert canary.get() == "in correct context" # This 'with' block handles the nursery fixture lifetime, the # teardone_done event, and crashing the context if there's an # unhandled exception. async with self._fixture_manager(test_ctx) as nursery_fixture: # Resolve our kwargs resolved_kwargs = {} for name, value in self._pytest_kwargs.items(): if isinstance(value, TrioFixture): await value.setup_done.wait() if value.fixture_value is NURSERY_FIXTURE_PLACEHOLDER: resolved_kwargs[name] = nursery_fixture else: resolved_kwargs[name] = value.fixture_value else: resolved_kwargs[name] = value # If something's already crashed before we're ready to start, then # there's no point in even setting up. if test_ctx.crashed: return # Run actual fixture setup step if self._is_test: # Tests are exactly like fixtures, except that they (1) have # to be regular async functions, (2) if there's a crash, we # should cancel them. assert not self.user_done_events func_value = None with trio.CancelScope() as cancel_scope: test_ctx.test_cancel_scope = cancel_scope assert not test_ctx.crashed await self._func(**resolved_kwargs) else: func_value = self._func(**resolved_kwargs) if isinstance(func_value, Coroutine): self.fixture_value = await func_value elif isasyncgen(func_value): self.fixture_value = await func_value.asend(None) elif isinstance(func_value, Generator): self.fixture_value = func_value.send(None) else: # Regular synchronous function self.fixture_value = func_value # Notify our users that self.fixture_value is ready self.setup_done.set() # Wait for users to be finished # # At this point we're in a very strange state: if the fixture # yielded inside a nursery or cancel scope, then we are still # "inside" that scope even though its with block is not on the # stack. In particular this means that if they get cancelled, then # our waiting might get a Cancelled error, that we cannot really # deal with – it should get thrown back into the fixture # generator, but pytest fixture generators don't work that way: # https://github.com/python-trio/pytest-trio/issues/55 # And besides, we can't start tearing down until all our users # have finished. # # So if we get an exception here, we crash the context (which # cancels the test and starts the cleanup process), save any # exception that *isn't* Cancelled (because if its Cancelled then # we can't route it to the right place, and anyway the teardown # code will get it again if it matters), and then use a shield to # keep waiting for the teardown to finish without having to worry # about cancellation. yield_outcome = outcome.Value(None) try: for event in self.user_done_events: await event.wait() except BaseException as exc: assert isinstance(exc, trio.Cancelled) yield_outcome = outcome.Error(exc) test_ctx.crash(self, None) with trio.CancelScope(shield=True): for event in self.user_done_events: await event.wait() # Do our teardown if isasyncgen(func_value): try: await yield_outcome.asend(func_value) except StopAsyncIteration: pass else: raise RuntimeError("too many yields in fixture") elif isinstance(func_value, Generator): try: yield_outcome.send(func_value) except StopIteration: pass else: raise RuntimeError("too many yields in fixture")