Skip to content

Optional call for decorators #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 9, 2020
4 changes: 0 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,6 @@ async/await fixtures
pytest fixture semantics of setup, value, and teardown. At present only
function and module scope are supported.

Note: You must *call* ``pytest_twisted.async_fixture()`` and
``pytest_twisted.async_yield_fixture()``.
This requirement may be removed in a future release.

.. code-block:: python

# No yield (coroutine function)
Expand Down
64 changes: 57 additions & 7 deletions pytest_twisted.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
import inspect
import itertools
import sys
import warnings

Expand Down Expand Up @@ -119,6 +120,56 @@ def decorator_apply(dec, func):
dict(decfunc=dec(func)), __wrapped__=func)


class DecoratorArgumentsError(Exception):
pass


def repr_args_kwargs(*args, **kwargs):
arguments = ', '.join(itertools.chain(
(repr(x) for x in args),
('{}={}'.format(k, repr(v)) for k, v in kwargs.items())
))

return '({})'.format(arguments)


def positional_not_allowed_exception(*args, **kwargs):
arguments = repr_args_kwargs(*args, **kwargs)

return DecoratorArgumentsError(
'Positional decorator arguments not allowed: {}'.format(arguments),
)


def _optional_arguments():
def decorator_decorator(d):
# TODO: this should get the signature of d minus the f or something
def decorator_wrapper(*args, **decorator_arguments):
"""this is decorator_wrapper"""
if len(args) > 1:
raise positional_not_allowed_exception()

if len(args) == 1:
maybe_f = args[0]

if len(decorator_arguments) > 0 or not callable(maybe_f):
raise positional_not_allowed_exception()

f = maybe_f
return d(f)

# TODO: this should get the signature of d minus the kwargs
def decorator_closure_on_arguments(f):
return d(f, **decorator_arguments)

return decorator_closure_on_arguments

return decorator_wrapper

return decorator_decorator
Comment on lines +144 to +169
Copy link
Member Author

@altendky altendky Jul 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically just @decorator.decorator...

altendky/qtrio#105



@_optional_arguments()
def inlineCallbacks(f):
"""
Mark as inline callbacks test for pytest-twisted processing and apply
Expand All @@ -135,6 +186,7 @@ def inlineCallbacks(f):
return decorated


@_optional_arguments()
def ensureDeferred(f):
"""
Mark as async test for pytest-twisted processing.
Expand Down Expand Up @@ -177,7 +229,8 @@ def _set_mark(o, mark):

def _marked_async_fixture(mark):
@functools.wraps(pytest.fixture)
def fixture(*args, **kwargs):
@_optional_arguments()
def fixture(f, *args, **kwargs):
try:
scope = args[0]
except IndexError:
Expand All @@ -197,13 +250,10 @@ def fixture(*args, **kwargs):
# https://github.com/pytest-dev/pytest-twisted/issues/56
raise AsyncFixtureUnsupportedScopeError.from_scope(scope=scope)

def decorator(f):
_set_mark(f, mark)
result = pytest.fixture(*args, **kwargs)(f)

return result
_set_mark(f, mark)
result = pytest.fixture(*args, **kwargs)(f)

return decorator
return result

return fixture

Expand Down
63 changes: 57 additions & 6 deletions testing/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,16 @@ def test_more_fail():
assert_outcomes(rr, {"failed": 1})


def test_inlineCallbacks(testdir, cmd_opts):
@pytest.fixture(
name="empty_optional_call",
params=["", "()"],
ids=["no call", "empty call"],
)
def empty_optional_call_fixture(request):
return request.param


def test_inlineCallbacks(testdir, cmd_opts, empty_optional_call):
test_file = """
from twisted.internet import reactor, defer
import pytest
Expand All @@ -244,19 +253,19 @@ def test_inlineCallbacks(testdir, cmd_opts):
def foo(request):
return request.param

@pytest_twisted.inlineCallbacks
@pytest_twisted.inlineCallbacks{optional_call}
def test_succeed(foo):
yield defer.succeed(foo)
if foo == "web":
raise RuntimeError("baz")
"""
""".format(optional_call=empty_optional_call)
testdir.makepyfile(test_file)
rr = testdir.run(*cmd_opts, timeout=timeout)
assert_outcomes(rr, {"passed": 2, "failed": 1})


@skip_if_no_async_await()
def test_async_await(testdir, cmd_opts):
def test_async_await(testdir, cmd_opts, empty_optional_call):
test_file = """
from twisted.internet import reactor, defer
import pytest
Expand All @@ -266,12 +275,12 @@ def test_async_await(testdir, cmd_opts):
def foo(request):
return request.param

@pytest_twisted.ensureDeferred
@pytest_twisted.ensureDeferred{optional_call}
async def test_succeed(foo):
await defer.succeed(foo)
if foo == "web":
raise RuntimeError("baz")
"""
""".format(optional_call=empty_optional_call)
testdir.makepyfile(test_file)
rr = testdir.run(*cmd_opts, timeout=timeout)
assert_outcomes(rr, {"passed": 2, "failed": 1})
Expand Down Expand Up @@ -384,6 +393,25 @@ def test_succeed_blue(foo):
assert_outcomes(rr, {"passed": 2, "failed": 1})


@skip_if_no_async_await()
def test_async_fixture_no_arguments(testdir, cmd_opts, empty_optional_call):
test_file = """
from twisted.internet import reactor, defer
import pytest
import pytest_twisted

@pytest_twisted.async_fixture{optional_call}
async def scope(request):
return request.scope

def test_is_function_scope(scope):
assert scope == "function"
""".format(optional_call=empty_optional_call)
testdir.makepyfile(test_file)
rr = testdir.run(*cmd_opts, timeout=timeout)
assert_outcomes(rr, {"passed": 1})


@skip_if_no_async_generators()
def test_async_yield_fixture_concurrent_teardown(testdir, cmd_opts):
test_file = """
Expand Down Expand Up @@ -461,6 +489,29 @@ def test_succeed(foo):
assert_outcomes(rr, {"passed": 4, "failed": 1, "errors": 2})


@skip_if_no_async_generators()
def test_async_yield_fixture_no_arguments(
testdir,
cmd_opts,
empty_optional_call,
):
test_file = """
from twisted.internet import reactor, defer
import pytest
import pytest_twisted

@pytest_twisted.async_yield_fixture{optional_call}
async def scope(request):
yield request.scope

def test_is_function_scope(scope):
assert scope == "function"
""".format(optional_call=empty_optional_call)
testdir.makepyfile(test_file)
rr = testdir.run(*cmd_opts, timeout=timeout)
assert_outcomes(rr, {"passed": 1})


@skip_if_no_async_generators()
def test_async_yield_fixture_function_scope(testdir, cmd_opts):
test_file = """
Expand Down