Skip to content

Commit 3687f39

Browse files
authored
Merge pull request #103 from altendky/optional_call_for_decorators
Optional call for decorators
2 parents 6823f4e + 3a627bf commit 3687f39

File tree

3 files changed

+114
-17
lines changed

3 files changed

+114
-17
lines changed

README.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,6 @@ async/await fixtures
159159
pytest fixture semantics of setup, value, and teardown. At present only
160160
function and module scope are supported.
161161

162-
Note: You must *call* ``pytest_twisted.async_fixture()`` and
163-
``pytest_twisted.async_yield_fixture()``.
164-
This requirement may be removed in a future release.
165-
166162
.. code-block:: python
167163
168164
# No yield (coroutine function)

pytest_twisted.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22
import inspect
3+
import itertools
34
import sys
45
import warnings
56

@@ -119,6 +120,56 @@ def decorator_apply(dec, func):
119120
dict(decfunc=dec(func)), __wrapped__=func)
120121

121122

123+
class DecoratorArgumentsError(Exception):
124+
pass
125+
126+
127+
def repr_args_kwargs(*args, **kwargs):
128+
arguments = ', '.join(itertools.chain(
129+
(repr(x) for x in args),
130+
('{}={}'.format(k, repr(v)) for k, v in kwargs.items())
131+
))
132+
133+
return '({})'.format(arguments)
134+
135+
136+
def positional_not_allowed_exception(*args, **kwargs):
137+
arguments = repr_args_kwargs(*args, **kwargs)
138+
139+
return DecoratorArgumentsError(
140+
'Positional decorator arguments not allowed: {}'.format(arguments),
141+
)
142+
143+
144+
def _optional_arguments():
145+
def decorator_decorator(d):
146+
# TODO: this should get the signature of d minus the f or something
147+
def decorator_wrapper(*args, **decorator_arguments):
148+
"""this is decorator_wrapper"""
149+
if len(args) > 1:
150+
raise positional_not_allowed_exception()
151+
152+
if len(args) == 1:
153+
maybe_f = args[0]
154+
155+
if len(decorator_arguments) > 0 or not callable(maybe_f):
156+
raise positional_not_allowed_exception()
157+
158+
f = maybe_f
159+
return d(f)
160+
161+
# TODO: this should get the signature of d minus the kwargs
162+
def decorator_closure_on_arguments(f):
163+
return d(f, **decorator_arguments)
164+
165+
return decorator_closure_on_arguments
166+
167+
return decorator_wrapper
168+
169+
return decorator_decorator
170+
171+
172+
@_optional_arguments()
122173
def inlineCallbacks(f):
123174
"""
124175
Mark as inline callbacks test for pytest-twisted processing and apply
@@ -135,6 +186,7 @@ def inlineCallbacks(f):
135186
return decorated
136187

137188

189+
@_optional_arguments()
138190
def ensureDeferred(f):
139191
"""
140192
Mark as async test for pytest-twisted processing.
@@ -177,7 +229,8 @@ def _set_mark(o, mark):
177229

178230
def _marked_async_fixture(mark):
179231
@functools.wraps(pytest.fixture)
180-
def fixture(*args, **kwargs):
232+
@_optional_arguments()
233+
def fixture(f, *args, **kwargs):
181234
try:
182235
scope = args[0]
183236
except IndexError:
@@ -197,13 +250,10 @@ def fixture(*args, **kwargs):
197250
# https://github.com/pytest-dev/pytest-twisted/issues/56
198251
raise AsyncFixtureUnsupportedScopeError.from_scope(scope=scope)
199252

200-
def decorator(f):
201-
_set_mark(f, mark)
202-
result = pytest.fixture(*args, **kwargs)(f)
203-
204-
return result
253+
_set_mark(f, mark)
254+
result = pytest.fixture(*args, **kwargs)(f)
205255

206-
return decorator
256+
return result
207257

208258
return fixture
209259

testing/test_basic.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,16 @@ def test_more_fail():
234234
assert_outcomes(rr, {"failed": 1})
235235

236236

237-
def test_inlineCallbacks(testdir, cmd_opts):
237+
@pytest.fixture(
238+
name="empty_optional_call",
239+
params=["", "()"],
240+
ids=["no call", "empty call"],
241+
)
242+
def empty_optional_call_fixture(request):
243+
return request.param
244+
245+
246+
def test_inlineCallbacks(testdir, cmd_opts, empty_optional_call):
238247
test_file = """
239248
from twisted.internet import reactor, defer
240249
import pytest
@@ -244,19 +253,19 @@ def test_inlineCallbacks(testdir, cmd_opts):
244253
def foo(request):
245254
return request.param
246255
247-
@pytest_twisted.inlineCallbacks
256+
@pytest_twisted.inlineCallbacks{optional_call}
248257
def test_succeed(foo):
249258
yield defer.succeed(foo)
250259
if foo == "web":
251260
raise RuntimeError("baz")
252-
"""
261+
""".format(optional_call=empty_optional_call)
253262
testdir.makepyfile(test_file)
254263
rr = testdir.run(*cmd_opts, timeout=timeout)
255264
assert_outcomes(rr, {"passed": 2, "failed": 1})
256265

257266

258267
@skip_if_no_async_await()
259-
def test_async_await(testdir, cmd_opts):
268+
def test_async_await(testdir, cmd_opts, empty_optional_call):
260269
test_file = """
261270
from twisted.internet import reactor, defer
262271
import pytest
@@ -266,12 +275,12 @@ def test_async_await(testdir, cmd_opts):
266275
def foo(request):
267276
return request.param
268277
269-
@pytest_twisted.ensureDeferred
278+
@pytest_twisted.ensureDeferred{optional_call}
270279
async def test_succeed(foo):
271280
await defer.succeed(foo)
272281
if foo == "web":
273282
raise RuntimeError("baz")
274-
"""
283+
""".format(optional_call=empty_optional_call)
275284
testdir.makepyfile(test_file)
276285
rr = testdir.run(*cmd_opts, timeout=timeout)
277286
assert_outcomes(rr, {"passed": 2, "failed": 1})
@@ -384,6 +393,25 @@ def test_succeed_blue(foo):
384393
assert_outcomes(rr, {"passed": 2, "failed": 1})
385394

386395

396+
@skip_if_no_async_await()
397+
def test_async_fixture_no_arguments(testdir, cmd_opts, empty_optional_call):
398+
test_file = """
399+
from twisted.internet import reactor, defer
400+
import pytest
401+
import pytest_twisted
402+
403+
@pytest_twisted.async_fixture{optional_call}
404+
async def scope(request):
405+
return request.scope
406+
407+
def test_is_function_scope(scope):
408+
assert scope == "function"
409+
""".format(optional_call=empty_optional_call)
410+
testdir.makepyfile(test_file)
411+
rr = testdir.run(*cmd_opts, timeout=timeout)
412+
assert_outcomes(rr, {"passed": 1})
413+
414+
387415
@skip_if_no_async_generators()
388416
def test_async_yield_fixture_concurrent_teardown(testdir, cmd_opts):
389417
test_file = """
@@ -461,6 +489,29 @@ def test_succeed(foo):
461489
assert_outcomes(rr, {"passed": 4, "failed": 1, "errors": 2})
462490

463491

492+
@skip_if_no_async_generators()
493+
def test_async_yield_fixture_no_arguments(
494+
testdir,
495+
cmd_opts,
496+
empty_optional_call,
497+
):
498+
test_file = """
499+
from twisted.internet import reactor, defer
500+
import pytest
501+
import pytest_twisted
502+
503+
@pytest_twisted.async_yield_fixture{optional_call}
504+
async def scope(request):
505+
yield request.scope
506+
507+
def test_is_function_scope(scope):
508+
assert scope == "function"
509+
""".format(optional_call=empty_optional_call)
510+
testdir.makepyfile(test_file)
511+
rr = testdir.run(*cmd_opts, timeout=timeout)
512+
assert_outcomes(rr, {"passed": 1})
513+
514+
464515
@skip_if_no_async_generators()
465516
def test_async_yield_fixture_function_scope(testdir, cmd_opts):
466517
test_file = """

0 commit comments

Comments
 (0)