unittest-parametrize 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,7 @@ import sys
5
5
  from collections.abc import Sequence
6
6
  from functools import wraps
7
7
  from types import FunctionType
8
- from typing import Any
9
- from typing import Callable
10
- from typing import TypeVar
8
+ from typing import Any, Callable, TypeVar
11
9
  from unittest import TestCase
12
10
 
13
11
  if sys.version_info >= (3, 10):
@@ -122,8 +120,8 @@ TestFunc = Callable[P, T]
122
120
 
123
121
  def parametrize(
124
122
  argnames: str | Sequence[str],
125
- argvalues: Sequence[tuple[Any, ...]] | Sequence[param],
126
- ids: Sequence[str | None] | None = None,
123
+ argvalues: Sequence[tuple[Any, ...] | param],
124
+ ids: Sequence[str | None] | Callable[[Any], str | None] | None = None,
127
125
  ) -> Callable[[Callable[P, T]], Callable[P, T]]:
128
126
  if isinstance(argnames, str):
129
127
  argnames = [a.strip() for a in argnames.split(",")]
@@ -131,24 +129,22 @@ def parametrize(
131
129
  if len(argnames) == 0:
132
130
  raise ValueError("argnames must contain at least one element")
133
131
 
134
- if ids is not None and len(ids) != len(argvalues):
132
+ ids_callable = callable(ids)
133
+ if ids is not None and not ids_callable and len(ids) != len(argvalues): # type: ignore[arg-type]
135
134
  raise ValueError("ids must have the same length as argvalues")
136
135
 
137
136
  seen_ids = set()
138
137
  params = []
139
138
  for i, argvalue in enumerate(argvalues):
140
- if ids and ids[i]:
141
- id_ = ids[i]
142
- else:
143
- id_ = str(i)
144
-
145
139
  if isinstance(argvalue, tuple):
146
140
  if len(argvalue) != len(argnames):
147
141
  raise ValueError(
148
142
  f"tuple at index {i} has wrong number of arguments "
149
143
  + f"({len(argvalue)} != {len(argnames)})"
150
144
  )
151
- params.append(param(*argvalue, id=id_))
145
+ argvalue = param(*argvalue, id=make_id(i, argvalue, ids))
146
+ params.append(argvalue)
147
+ seen_ids.add(argvalue.id)
152
148
  elif isinstance(argvalue, param):
153
149
  if len(argvalue.args) != len(argnames):
154
150
  raise ValueError(
@@ -157,7 +153,7 @@ def parametrize(
157
153
  )
158
154
 
159
155
  if argvalue.id is None:
160
- argvalue = param(*argvalue.args, id=id_)
156
+ argvalue = param(*argvalue.args, id=make_id(i, argvalue, ids))
161
157
  if argvalue.id in seen_ids:
162
158
  raise ValueError(f"Duplicate param id {argvalue.id!r}")
163
159
  seen_ids.add(argvalue.id)
@@ -183,3 +179,34 @@ def parametrize(
183
179
  return func
184
180
 
185
181
  return wrapper
182
+
183
+
184
+ def make_id(
185
+ i: int,
186
+ argvalue: tuple[Any, ...] | param,
187
+ ids: Sequence[str | None] | Callable[[Any], str | None] | None,
188
+ ) -> str:
189
+ if callable(ids):
190
+ if isinstance(argvalue, tuple):
191
+ values = argvalue
192
+ else:
193
+ values = argvalue.args
194
+
195
+ id_parts = []
196
+ for value in values:
197
+ id_part = ids(value)
198
+ if id_part is not None:
199
+ id_parts.append(id_part)
200
+ else:
201
+ id_parts.append(str(value))
202
+ id_ = "_".join(id_parts)
203
+ # Validate the generated ID
204
+ if not f"_{id_}".isidentifier():
205
+ raise ValueError(
206
+ f"callable ids returned invalid Python identifier suffix: {id_!r}"
207
+ )
208
+ return id_
209
+ elif ids and ids[i]:
210
+ return str(ids[i])
211
+ else:
212
+ return str(i)
@@ -1,15 +1,15 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: unittest-parametrize
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Parametrize tests within unittest TestCases.
5
5
  Author-email: Adam Johnson <me@adamj.eu>
6
+ License-Expression: MIT
6
7
  Project-URL: Changelog, https://github.com/adamchainz/unittest-parametrize/blob/main/CHANGELOG.rst
7
8
  Project-URL: Funding, https://adamj.eu/books/
8
9
  Project-URL: Repository, https://github.com/adamchainz/unittest-parametrize
9
10
  Keywords: unittest
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Classifier: Programming Language :: Python :: 3.9
@@ -22,6 +22,7 @@ Requires-Python: >=3.9
22
22
  Description-Content-Type: text/x-rst
23
23
  License-File: LICENSE
24
24
  Requires-Dist: typing-extensions; python_version < "3.10"
25
+ Dynamic: license-file
25
26
 
26
27
  ====================
27
28
  unittest-parametrize
@@ -83,8 +84,7 @@ Here’s a basic example:
83
84
 
84
85
  .. code-block:: python
85
86
 
86
- from unittest_parametrize import parametrize
87
- from unittest_parametrize import ParametrizedTestCase
87
+ from unittest_parametrize import ParametrizedTestCase, parametrize
88
88
 
89
89
 
90
90
  class SquareTests(ParametrizedTestCase):
@@ -113,8 +113,7 @@ You can provide argument names as a sequence of strings instead:
113
113
 
114
114
  .. code-block:: python
115
115
 
116
- from unittest_parametrize import parametrize
117
- from unittest_parametrize import ParametrizedTestCase
116
+ from unittest_parametrize import ParametrizedTestCase, parametrize
118
117
 
119
118
 
120
119
  class SquareTests(ParametrizedTestCase):
@@ -177,13 +176,20 @@ You can see these names when running the tests:
177
176
 
178
177
  OK
179
178
 
180
- You can customize these names by passing ``param`` objects, which contain the arguments and an optional ID for the suffix:
179
+ You can customize these names in several ways:
180
+
181
+ 1. Using ``param`` objects with IDs.
182
+ 2. Passing a sequence of strings as the ``ids`` argument.
183
+ 3. Passing a callable as the ``ids`` argument.
184
+
185
+ Passing ``param`` objects with IDs
186
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
187
+
188
+ Pass a ``param`` object for each parameter set, setting the test ID suffix with the optional ``id`` argument:
181
189
 
182
190
  .. code-block:: python
183
191
 
184
- from unittest_parametrize import param
185
- from unittest_parametrize import parametrize
186
- from unittest_parametrize import ParametrizedTestCase
192
+ from unittest_parametrize import ParametrizedTestCase, param, parametrize
187
193
 
188
194
 
189
195
  class SquareTests(ParametrizedTestCase):
@@ -197,7 +203,7 @@ You can customize these names by passing ``param`` objects, which contain the ar
197
203
  def test_square(self, x: int, expected: int) -> None:
198
204
  self.assertEqual(x**2, expected)
199
205
 
200
- Yielding perhaps more natural names:
206
+ Yielding more natural names:
201
207
 
202
208
  .. code-block:: console
203
209
 
@@ -216,9 +222,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
216
222
 
217
223
  .. code-block:: python
218
224
 
219
- from unittest_parametrize import param
220
- from unittest_parametrize import parametrize
221
- from unittest_parametrize import ParametrizedTestCase
225
+ from unittest_parametrize import ParametrizedTestCase, param, parametrize
222
226
 
223
227
 
224
228
  class SquareTests(ParametrizedTestCase):
@@ -232,7 +236,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
232
236
  def test_square(self, x: int, expected: int) -> None:
233
237
  self.assertEqual(x**2, expected)
234
238
 
235
- ID-free ``param``\s fall back to the default index suffixes:
239
+ The ID-free ``param``\s fall back to the default index suffixes:
236
240
 
237
241
  .. code-block:: console
238
242
 
@@ -245,12 +249,14 @@ ID-free ``param``\s fall back to the default index suffixes:
245
249
 
246
250
  OK
247
251
 
248
- Alternatively, you can provide the id’s separately with the ``ids`` argument:
252
+ Passing a sequence of strings as the ``ids`` argument
253
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
254
+
255
+ Another option is to provide the IDs in the separate ``ids`` argument:
249
256
 
250
257
  .. code-block:: python
251
258
 
252
- from unittest_parametrize import parametrize
253
- from unittest_parametrize import ParametrizedTestCase
259
+ from unittest_parametrize import ParametrizedTestCase, parametrize
254
260
 
255
261
 
256
262
  class SquareTests(ParametrizedTestCase):
@@ -265,6 +271,64 @@ Alternatively, you can provide the id’s separately with the ``ids`` argument:
265
271
  def test_square(self, x: int, expected: int) -> None:
266
272
  self.assertEqual(x**2, expected)
267
273
 
274
+ This option sets the full suffixes to the provided strings:
275
+
276
+ .. code-block:: console
277
+
278
+ $ python -m unittest t.py -v
279
+ test_square_one (example.SquareTests.test_square_one) ... ok
280
+ test_square_two (example.SquareTests.test_square_two) ... ok
281
+
282
+ ----------------------------------------------------------------------
283
+ Ran 2 tests in 0.000s
284
+
285
+ OK
286
+
287
+ Passing a callable as the ``ids`` argument
288
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
289
+
290
+ The ``ids`` argument can also be a callable, which unittest-parametrize calls once per parameter value.
291
+ The callable can return a string for that value, or ``None`` to use the default index suffix.
292
+ The values are then joined with underscores to form the full suffix.
293
+
294
+ For example:
295
+
296
+ .. code-block:: python
297
+
298
+ from unittest_parametrize import ParametrizedTestCase, parametrize
299
+
300
+
301
+ def make_id(value):
302
+ if isinstance(value, int):
303
+ return f"num{value}"
304
+ return None
305
+
306
+
307
+ class SquareTests(ParametrizedTestCase):
308
+ @parametrize(
309
+ "x,expected",
310
+ [
311
+ (1, 1),
312
+ (2, 4),
313
+ ],
314
+ ids=make_id,
315
+ )
316
+ def test_square(self, x: int, expected: int) -> None:
317
+ self.assertEqual(x**2, expected)
318
+
319
+ …yields:
320
+
321
+ .. code-block:: console
322
+
323
+ $ python -m unittest t.py -v
324
+ test_square_num1_num1 (example.SquareTests.test_square_num1_num1) ... ok
325
+ test_square_num2_num4 (example.SquareTests.test_square_num2_num4) ... ok
326
+
327
+ ----------------------------------------------------------------------
328
+ Ran 2 tests in 0.000s
329
+
330
+ OK
331
+
268
332
  Use with other test decorators
269
333
  ------------------------------
270
334
 
@@ -275,8 +339,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
275
339
  .. code-block:: python
276
340
 
277
341
  from unittest import mock
278
- from unittest_parametrize import parametrize
279
- from unittest_parametrize import ParametrizedTestCase
342
+ from unittest_parametrize import ParametrizedTestCase, parametrize
280
343
 
281
344
 
282
345
  class CarpentryTests(ParametrizedTestCase):
@@ -285,8 +348,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
285
348
  [(11,), (17,)],
286
349
  )
287
350
  @mock.patch("example.hammer", autospec=True)
288
- def test_nail_a_board(self, mock_hammer, nails):
289
- ...
351
+ def test_nail_a_board(self, mock_hammer, nails): ...
290
352
 
291
353
  Also note that due to how ``mock.patch`` always adds positional arguments at the start, the parametrized arguments must come last.
292
354
  ``@parametrize`` always adds parameters as keyword arguments, so you can also use `keyword-only syntax <https://peps.python.org/pep-3102/>`__ for parametrized arguments:
@@ -294,8 +356,7 @@ Also note that due to how ``mock.patch`` always adds positional arguments at the
294
356
  .. code-block:: python
295
357
 
296
358
  # ...
297
- def test_nail_a_board(self, mock_hammer, *, nails):
298
- ...
359
+ def test_nail_a_board(self, mock_hammer, *, nails): ...
299
360
 
300
361
  Multiple ``@parametrize`` decorators
301
362
  ------------------------------------
@@ -305,8 +366,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
305
366
 
306
367
  .. code-block:: python
307
368
 
308
- from unittest_parametrize import parametrize
309
- from unittest_parametrize import ParametrizedTestCase
369
+ from unittest_parametrize import ParametrizedTestCase, parametrize
310
370
 
311
371
 
312
372
  class RocketTests(ParametrizedTestCase):
@@ -318,8 +378,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
318
378
  for hyperdrive_level in [0, 1, 2]
319
379
  ],
320
380
  )
321
- def test_takeoff(self, use_ions, hyperdrive_level) -> None:
322
- ...
381
+ def test_takeoff(self, use_ions, hyperdrive_level) -> None: ...
323
382
 
324
383
  The above creates 2 * 3 = 6 versions of ``test_takeoff``.
325
384
 
@@ -331,8 +390,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
331
390
  .. code-block:: python
332
391
 
333
392
  from itertools import product
334
- from unittest_parametrize import parametrize
335
- from unittest_parametrize import ParametrizedTestCase
393
+ from unittest_parametrize import ParametrizedTestCase, parametrize
336
394
 
337
395
 
338
396
  class RocketTests(ParametrizedTestCase):
@@ -346,8 +404,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
346
404
  )
347
405
  ),
348
406
  )
349
- def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
350
- ...
407
+ def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None: ...
351
408
 
352
409
  The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
353
410
 
@@ -371,15 +428,47 @@ To parametrize all tests within a test case, create a separate decorator and app
371
428
 
372
429
  class StatsTests(ParametrizedTestCase):
373
430
  @parametrize_race
374
- def test_strength(self, race: str) -> None:
375
- ...
431
+ def test_strength(self, race: str) -> None: ...
376
432
 
377
433
  @parametrize_race
378
- def test_dexterity(self, race: str) -> None:
379
- ...
434
+ def test_dexterity(self, race: str) -> None: ...
380
435
 
381
436
  ...
382
437
 
438
+ Pass parameters in a dataclass
439
+ ------------------------------
440
+
441
+ Thanks to `Florian Bruhin <https://bruhin.software/>`__ for this tip, from his `pytest tips and tricks presentation <https://bruhin.software/>`__.
442
+
443
+ If your test uses many parameters or cases, the parametrization may become unwieldy, as cases don’t name the arguments.
444
+ In this case, try using a `dataclass <https://docs.python.org/3/library/dataclasses.html>`__ to hold the arguments:
445
+
446
+ .. code-block:: python
447
+
448
+ from dataclasses import dataclass
449
+
450
+ from unittest_parametrize import ParametrizedTestCase, parametrize
451
+
452
+
453
+ @dataclass
454
+ class SquareParams:
455
+ x: int
456
+ expected: int
457
+
458
+
459
+ class SquareTests(ParametrizedTestCase):
460
+ @parametrize(
461
+ "sp",
462
+ [
463
+ (SquareParams(x=1, expected=1),),
464
+ (SquareParams(x=2, expected=4),),
465
+ ],
466
+ )
467
+ def test_square(self, sp: SquareParams) -> None:
468
+ self.assertEqual(sp.x**2, sp.expected)
469
+
470
+ This way, each parameter is type-checked and named, improving safety and readability.
471
+
383
472
  History
384
473
  =======
385
474
 
@@ -0,0 +1,7 @@
1
+ unittest_parametrize/__init__.py,sha256=32m2wHL1et5GcNetK-RA3FVksCf_5ChauRxVK3l2vJc,7234
2
+ unittest_parametrize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ unittest_parametrize-1.7.0.dist-info/licenses/LICENSE,sha256=Jn179RflkRZXhSCGt_D7asLy2Ex47C0WdBHxe0lBazk,1069
4
+ unittest_parametrize-1.7.0.dist-info/METADATA,sha256=iD6hm8ej4zGlHDAvI_4WwPKVyIzS2-U_zrqPYDYOVSY,17807
5
+ unittest_parametrize-1.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ unittest_parametrize-1.7.0.dist-info/top_level.txt,sha256=4nLqNaGtBX8Ny36ZdFt37Gk-87hPtOvfdfZlTBi3OtU,21
7
+ unittest_parametrize-1.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.7.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,7 +0,0 @@
1
- unittest_parametrize/__init__.py,sha256=-WNbay-GoS2_ENp9cOgE3pf_BJH2Nr1eX7wiAwJyxic,6299
2
- unittest_parametrize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- unittest_parametrize-1.6.0.dist-info/LICENSE,sha256=Jn179RflkRZXhSCGt_D7asLy2Ex47C0WdBHxe0lBazk,1069
4
- unittest_parametrize-1.6.0.dist-info/METADATA,sha256=kZJe0NHStrHyHJgkttwwmGgBOagnfB4XfffeK_SspUI,15223
5
- unittest_parametrize-1.6.0.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
6
- unittest_parametrize-1.6.0.dist-info/top_level.txt,sha256=4nLqNaGtBX8Ny36ZdFt37Gk-87hPtOvfdfZlTBi3OtU,21
7
- unittest_parametrize-1.6.0.dist-info/RECORD,,