1 import contextlib

2

3 import pytest as pytest

4

5 import onionizer

6

7

8@pytest.fixture

9 def func_that_adds():

10 def func(x: int, y: int) -> int:

11 return x + y

12

13 return func

14

15

16 def test_mutate_arguments(func_that_adds):

17 def middleware1(x: int, y: int) -> onionizer.OnionGenerator[int]:

18 result = yield (x + 1, y + 1), {}

19 return result

20

21 def middleware2(x: int, y: int) -> onionizer.OnionGenerator[int]:

22 result = yield (x, y + 1), {}

23 return result

24

25 wrapped_func = onionizer.wrap_around(func_that_adds, [middleware1, middleware2])

26 result = wrapped_func(0, 0)

27

28 assert result == 3

29

30

31 def test_mutate_output(func_that_adds):

32 def middleware1(x: int, y: int) -> onionizer.OnionGenerator[int]:

33 result = yield onionizer.UNCHANGED

34 return result + 1

35

36 def middleware2(x: int, y: int) -> onionizer.OnionGenerator[int]:

37 result = yield onionizer.UNCHANGED

38 return result * 2

39

40 wrapped_func = onionizer.wrap_around(func_that_adds, [middleware1, middleware2])

41 result = wrapped_func(0, 0)

42 assert result == 1

43

44 wrapped_func = onionizer.wrap_around(func_that_adds, [middleware2, middleware1])

45 result = wrapped_func(0, 0)

46 assert result == 2

47

48

49 def test_pos_only(func_that_adds):

50 def middleware1(x: int, y: int):

51 result = yield onionizer.PositionalArgs(x + 1, y)

52 return result

53

54 def middleware2(x: int, y: int):

55 result = yield onionizer.PositionalArgs(x, y + 1)

56 return result

57

58 wrapped_func = onionizer.wrap_around(func_that_adds, [middleware1, middleware2])

59 result = wrapped_func(0, 0)

60 assert result == 2

61

62

63 def test_kw_only(func_that_adds):

64 def middleware1(x: int, y: int):

65 result = yield onionizer.KeywordArgs({"x": x + 1, "y": y})

66 return result

67

68 def middleware2(x: int, y: int):

69 result = yield onionizer.KeywordArgs({"x": x, "y": y + 1})

70 return result

71

72 wrapped_func = onionizer.wrap_around(func_that_adds, [middleware1, middleware2])

73 result = wrapped_func(x=0, y=0)

74 assert result == 2

75

76

77 def test_preprocessor(func_that_adds):

78 @onionizer.preprocessor

79 def midd1(x: int, y: int):

80 return onionizer.PositionalArgs(x + 1, y + 1)

81

82 assert midd1.__name__ == "midd1"

83

84 wrapped_func = onionizer.wrap_around(func_that_adds, [midd1])

85 result = wrapped_func(x=0, y=0)

86 assert result == 2

87

88

89 def test_postprocessor(func_that_adds):

90 @onionizer.postprocessor

91 def midd1(val: int):

92 return val ** 2

93

94 assert midd1.__name__ == "midd1"

95

96 wrapped_func = onionizer.wrap_around(func_that_adds, [midd1])

97 result = wrapped_func(x=1, y=1)

98 assert result == 4

99

100

101 def test_postprocessor_with_multiple_values():

102 def dummy_func(x, y):

103 return x, y

104

105 @onionizer.postprocessor

106 def midd1(couple: tuple):

107 c1, c2 = couple

108 return c2, c1

109

110 wrapped_func = onionizer.wrap_around(dummy_func, [midd1])

111 result = wrapped_func(x=1, y=2)

112 assert result == (2, 1)

113

114

115 def test_support_for_context_managers():

116 def func(x, y):

117 return x / y

118

119 @onionizer.postprocessor

120 def midd1(val):

121 return val + 1

122

123 @contextlib.contextmanager

124 def exception_catcher():

125 try:

126 yield

127 except Exception as e:

128 raise RuntimeError("Exception caught") from e

129

130 wrapped_func = onionizer.wrap_around(func, [exception_catcher()])

131 with pytest.raises(RuntimeError) as e:

132 wrapped_func(x=1, y=0)

133 assert str(e.value) == "Exception caught"

134

135 another_wrapped_func = onionizer.wrap_around(func, [exception_catcher(), midd1])

136 assert another_wrapped_func(x=1, y=1) == 2

137

138

139 def test_decorator():

140 def middleware1(x, y):

141 result = yield onionizer.KeywordArgs({"x": x + 1, "y": y})

142 return result

143

144 def middleware2(x, y):

145 result = yield onionizer.KeywordArgs({"x": x, "y": y + 1})

146 return result

147

148 @onionizer.decorate([middleware1, middleware2])

149 def func(x, y):

150 return x + y

151

152 @onionizer.decorate(middleware1)

153 def func2(x, y):

154 return x + y

155

156 result = func(x=0, y=0)

157 assert result == 2

158

159 result2 = func2(x=0, y=0)

160 assert result2 == 1

161

162

163 def test_incorrect_decorator():

164 with pytest.raises(TypeError) as e:

165

166 @onionizer.decorate(1)

167 def func2(x, y):

168 return x + y

169

170 assert (

171 str(e.value) == "middlewares must be a list of coroutines or a single coroutine"

172 )

173

174

175 def test_as_decorator():

176 @onionizer.as_decorator

177 def middleware1(x, y):

178 result = yield onionizer.KeywordArgs({"x": x + 1, "y": y})

179 return result

180

181 @onionizer.as_decorator

182 def middleware2(x, y):

183 result = yield onionizer.KeywordArgs({"x": x, "y": y + 1})

184 return result

185

186 @middleware1

187 @middleware2

188 def func(x, y):

189 return x + y

190

191 result = func(x=0, y=0)

192 assert result == 2

193

194

195 def test_uncompatible_signature(func_that_adds):

196 def middleware1(*args):

197 result = yield onionizer.UNCHANGED

198 return result

199

200 with pytest.raises(ValueError):

201 onionizer.wrap_around(func_that_adds, middlewares=[middleware1])

202

203

204 def test_uncompatible_signature_but_disable_sigcheck(func_that_adds):

205 def middleware1(*args):

206 result = yield onionizer.UNCHANGED

207 return result

208

209 onionizer.wrap_around(func_that_adds, middlewares=[middleware1], sigcheck=False)

210 assert True

211

212

213 def test_unyielding_middleware(func_that_adds):

214 def middleware1(*args):

215 return "hello"

216

217 f2 = onionizer.wrap_around(

218 func_that_adds, middlewares=[middleware1], sigcheck=False

219 )

220 with pytest.raises(TypeError) as e:

221 f2(1, 2)

222 assert (

223 str(e.value) == "Middleware middleware1 is not a coroutine. "

224 "Did you forget to use a yield statement?"

225 )

226

227

228 def test_tooyielding_middleware(func_that_adds):

229 def middleware1(*args):

230 yield onionizer.UNCHANGED

231 yield onionizer.UNCHANGED

232

233 f2 = onionizer.wrap_around(

234 func_that_adds, middlewares=[middleware1], sigcheck=False

235 )

236 with pytest.raises(RuntimeError) as e:

237 f2(1, 2)

238 assert (

239 str(e.value)

240 == "Generator did not exhaust. Your function should yield exactly once."

241 )

242

243

244 def test_incorrects_managers(func_that_adds):

245 class MyManager:

246 def __enter__(self):

247 return self

248

249 f = onionizer.wrap_around(func_that_adds, middlewares=[MyManager()], sigcheck=False)

250 with pytest.raises(TypeError):

251 f(1, 2)

252 f2 = onionizer.wrap_around(

253 func_that_adds, middlewares=[MyManager()], sigcheck=False

254 )

255 with pytest.raises(TypeError):

256 f2(1, 2)

257

258

259 def test_incorrect_func():

260 with pytest.raises(TypeError) as e:

261 onionizer.wrap_around(1, [])

262 assert str(e.value) == "func must be callable"

263

264

265 def test_incorrect_midlist(func_that_adds):

266 def middleware1(*args):

267 result = yield onionizer.UNCHANGED

268 return result

269

270 with pytest.raises(TypeError) as e:

271 onionizer.wrap_around(func_that_adds, middlewares=middleware1)

272 assert str(e.value) == "middlewares must be a list of coroutines"

273

274

275 def test_incorrect_yields(func_that_adds):

276 def middleware1(x: int, y: int):

277 yield 2

278 return 1

279

280 with pytest.raises(TypeError) as e:

281 onionizer.wrap_around(func_that_adds, middlewares=[middleware1])(1, 2)

282 assert (

283 str(e.value) == "arguments must be a tuple of length 2, "

284 "maybe use onionizer.PositionalArgs or onionizer.MixedArgs instead"

285 )