Skip to content

Commit edf52fa

Browse files
author
Sylvain MARIE
committed
is_lazy is now part of public API, and _LazyValue now has a cache mechanism like _LazyTuple. Fixes #143
1 parent 492bf80 commit edf52fa

File tree

3 files changed

+76
-29
lines changed

3 files changed

+76
-29
lines changed

pytest_cases/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# + All contributors to <https://github.com/smarie/python-pytest-cases>
33
#
44
# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
5-
from .common_pytest_lazy_values import lazy_value
5+
from .common_pytest_lazy_values import lazy_value, is_lazy
66
from .common_others import unfold_expected_err, assert_exception, AUTO, AUTO2
77

88
from .fixture_core1_unions import fixture_union, NOT_USED, unpack_fixture, ignore_unused
@@ -46,7 +46,7 @@
4646
# -- fixture core2
4747
'pytest_fixture_plus', 'fixture_plus', 'fixture', 'param_fixtures', 'param_fixture',
4848
# -- fixture parametrize plus
49-
'pytest_parametrize_plus', 'parametrize_plus', 'parametrize', 'fixture_ref', 'lazy_value',
49+
'pytest_parametrize_plus', 'parametrize_plus', 'parametrize', 'fixture_ref', 'lazy_value', 'is_lazy',
5050

5151
# V1 - DEPRECATED symbols
5252
# --cases_funcs

pytest_cases/common_pytest_lazy_values.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,24 @@ class _LazyValue(Lazy):
125125
fixture and therefore can neither be parametrized nor depend on fixtures. It should have no mandatory argument.
126126
"""
127127
if pytest53:
128-
__slots__ = 'valuegetter', '_id', '_marks'
128+
__slots__ = 'valuegetter', '_id', '_marks', 'retrieved', 'value'
129129
_field_names = __slots__
130130
else:
131-
# we can not define __slots__ since we extend int,
131+
# we can not define __slots__ since we'll extend int in a subclass
132132
# see https://docs.python.org/3/reference/datamodel.html?highlight=__slots__#notes-on-using-slots
133-
_field_names = 'valuegetter', '_id', '_marks'
133+
_field_names = 'valuegetter', '_id', '_marks', 'retrieved', 'value'
134134

135135
@classmethod
136136
def copy_from(cls,
137137
obj # type: _LazyValue
138138
):
139139
"""Creates a copy of this _LazyValue"""
140-
return cls(valuegetter=obj.valuegetter, id=obj._id, marks=obj._marks)
140+
new_obj = cls(valuegetter=obj.valuegetter, id=obj._id, marks=obj._marks)
141+
# make sure the copy will not need to retrieve the result if already done
142+
new_obj.retrieved = obj.retrieved
143+
if new_obj.retrieved:
144+
new_obj.value = obj.value
145+
return new_obj
141146

142147
# noinspection PyMissingConstructor
143148
def __init__(self,
@@ -151,6 +156,8 @@ def __init__(self,
151156
self._marks = marks
152157
else:
153158
self._marks = (marks, )
159+
self.retrieved = False
160+
self.value = None
154161

155162
def get_marks(self, as_decorators=False):
156163
"""
@@ -186,13 +193,24 @@ def get_id(self):
186193
return vg.__name__
187194

188195
def get(self):
189-
return self.valuegetter()
196+
""" Call the underlying value getter, then return the result value (not self). With a cache mechanism """
197+
if not self.retrieved:
198+
# retrieve
199+
self.value = self.valuegetter()
200+
self.retrieved = True
201+
202+
return self.value
190203

191204
def as_lazy_tuple(self, nb_params):
192-
return LazyTuple(self, nb_params)
205+
res = LazyTuple(self, nb_params)
206+
if self.retrieved:
207+
# make sure the tuple will not need to retrieve the result if already done
208+
res.retrieved = True
209+
res.value = self.value
210+
return res
193211

194212
def as_lazy_items_list(self, nb_params):
195-
return [v for v in LazyTuple(self, nb_params)]
213+
return [v for v in self.as_lazy_tuple(nb_params)]
196214

197215

198216
class _LazyTupleItem(Lazy):
@@ -203,7 +221,7 @@ class _LazyTupleItem(Lazy):
203221
__slots__ = 'host', 'item'
204222
_field_names = __slots__
205223
else:
206-
# we can not define __slots__ since we extend int,
224+
# we can not define __slots__ since we'll extend int in a subclass
207225
# see https://docs.python.org/3/reference/datamodel.html?highlight=__slots__#notes-on-using-slots
208226
_field_names = 'host', 'item'
209227

@@ -258,6 +276,7 @@ def copy_from(cls,
258276
obj # type: LazyTuple
259277
):
260278
new_obj = cls(valueref=obj.value, theoretical_size=obj.theoretical_size)
279+
# make sure the copy will not need to retrieve the result if already done
261280
new_obj.retrieved = obj.retrieved
262281
if new_obj.retrieved:
263282
new_obj.value = obj.value
@@ -281,7 +300,7 @@ def get_id(self):
281300
return self.valuegetter.get_id()
282301

283302
def get(self):
284-
""" Call the underlying value getter, then return the tuple (not self) """
303+
""" Call the underlying value getter, then return the result tuple (not self). With a cache mechanism """
285304
if not self.retrieved:
286305
# retrieve
287306
self.value = self.valuegetter.get()

pytest_cases/tests/pytest_extension/parametrize_plus/test_lazy_value_low_level.py

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@
55
from distutils.version import LooseVersion
66

77
import pytest
8+
from pytest_cases.common_pytest_lazy_values import LazyValue, LazyTuple
89

910
from pytest_cases import lazy_value
1011
from pytest_cases.common_pytest import mini_idval
1112

1213

14+
_called = 0
15+
16+
1317
def test_value_ref():
1418
"""
1519
Tests that our hack for lazy_value works: for old versions of pytest, we make it a subclass
1620
of int so that pytest id generator (`_idval`) calls "str" on it. For pytest >= 5.3.0 we do not need this hack
1721
as overriding __name__ is enough.
1822
:return:
1923
"""
24+
global _called
25+
2026
def foo():
27+
global _called
28+
_called += 1
2129
return 1, 2
2230

2331
a = lazy_value(foo)
@@ -26,25 +34,45 @@ def foo():
2634
assert mini_idval(a, 'a', 1) == 'foo'
2735
assert 'LazyValue' in repr(a)
2836

37+
# test that that calls work and the cache works even across copies
38+
assert a.get() == (1, 2)
39+
assert a.get() == (1, 2)
40+
assert LazyValue.copy_from(a).get() == (1, 2)
41+
assert _called == 1
42+
2943
# now do the same test for lazy values used as a tuple of parameters
30-
at = lazy_value(foo).as_lazy_tuple(2)
31-
32-
# test when the tuple is unpacked into several parameters
33-
for i, a in enumerate(at):
34-
# test that ids will be correctly generated even on old pytest
35-
assert mini_idval(a, 'a', 1) == 'foo[%s]' % i
36-
assert ('LazyTupleItem(item=%s' % i) in repr(a)
37-
38-
# test when the tuple is not unpacked -
39-
# note: this is not supposed to happen when @parametrize decorates a test function,
40-
# it only happens when @parametrize decorates a fixture - indeed in that case we generate the whole id ourselves
41-
assert str(at) == 'foo'
42-
assert not at.retrieved
43-
# test that retrieving the tuple does not loose the id
44-
v = at.get()
45-
assert v == (1, 2)
46-
assert str(at) == 'foo'
47-
assert at.retrieved
44+
new_lv = lazy_value(foo)
45+
assert not new_lv.retrieved
46+
assert a.retrieved
47+
48+
for src in new_lv, a:
49+
_called = 0 if not src.retrieved else 1
50+
at = src.as_lazy_tuple(2)
51+
52+
# test when the tuple is unpacked into several parameters
53+
if not at.retrieved:
54+
for i, a in enumerate(at):
55+
# test that ids will be correctly generated even on old pytest
56+
assert mini_idval(a, 'a', 1) == 'foo[%s]' % i
57+
assert ('LazyTupleItem(item=%s' % i) in repr(a)
58+
else:
59+
# this does not happen in real usage but in case a plugin messes with this
60+
assert tuple(at) == (1, 2)
61+
62+
# test when the tuple is not unpacked -
63+
# note: this is not supposed to happen when @parametrize decorates a test function,
64+
# it only happens when @parametrize decorates a fixture - indeed in that case we generate the whole id ourselves
65+
assert str(at) == 'foo'
66+
67+
# assert that calls work and the cache works even across copies
68+
assert at.get() == (1, 2)
69+
assert at.get() == (1, 2)
70+
assert LazyTuple.copy_from(at).get() == (1, 2)
71+
assert _called == 1
72+
73+
# test that retrieving the tuple does not loose the id
74+
assert str(at) == 'foo'
75+
assert at.retrieved
4876

4977

5078
pytest53 = LooseVersion(pytest.__version__) >= LooseVersion("5.3.0")

0 commit comments

Comments
 (0)