|
1 | 1 | import logging
|
2 | 2 | from collections import ChainMap, defaultdict
|
3 |
| -from collections.abc import Callable, Collection, Mapping, MutableMapping, Sequence |
| 3 | +from collections.abc import ( |
| 4 | + Callable, |
| 5 | + Collection, |
| 6 | + Iterable, |
| 7 | + Mapping, |
| 8 | + MutableMapping, |
| 9 | + Sequence, |
| 10 | +) |
4 | 11 | from datetime import datetime
|
5 | 12 | from functools import partial, singledispatch, update_wrapper
|
6 | 13 | from importlib.metadata import version
|
@@ -547,16 +554,19 @@ def merge_data(base: "MutableParameterData", *others: "ParameterData") -> None:
|
547 | 554 | base[par] = pd.concat([base.get(par, None), df])
|
548 | 555 |
|
549 | 556 |
|
550 |
| -def minimum_version(expr: str) -> Callable: |
| 557 | +def minimum_version( |
| 558 | + expr: str, raises: Optional[Iterable[type[Exception]]] = None |
| 559 | +) -> Callable: |
551 | 560 | """Decorator for functions that require a minimum version of some upstream package.
|
552 | 561 |
|
553 | 562 | If the decorated function is called and the condition in `expr` is not met,
|
554 | 563 | :class:`.NotImplementedError` is raised with an informative message.
|
555 | 564 |
|
556 |
| - The decorated function gains an attribute :py:`.minimum_version`, another decorator |
557 |
| - that can be used on associated test code. This marks the test as XFAIL, raising |
558 |
| - :class:`.NotImplementedError` or :class:`.RuntimeError` (e.g. for :mod:`.click` |
559 |
| - testing). |
| 565 | + The decorated function gains an attribute :py:`.minimum_version`, a pytest |
| 566 | + MarkDecorator that can be used on associated test code. This marks the test as |
| 567 | + XFAIL, raising :class:`.NotImplementedError` (directly); :class:`.RuntimeError` or |
| 568 | + :class:`.AssertionError` (for instance, via :mod:`.click` test utilities), or any |
| 569 | + of the classes given in the `raises` argument. |
560 | 570 |
|
561 | 571 | See :func:`.prepare_reporter` / :func:`.test_prepare_reporter` for a usage example.
|
562 | 572 |
|
@@ -586,28 +596,26 @@ def wrapper(*args, **kwargs):
|
586 | 596 |
|
587 | 597 | update_wrapper(wrapper, func)
|
588 | 598 |
|
589 |
| - # Create a test function decorator |
590 |
| - def marker(test_func): |
591 |
| - # Import pytest only when there is a test function to mark |
| 599 | + try: |
592 | 600 | import pytest
|
593 | 601 |
|
594 |
| - # Create the mark |
595 |
| - mark = pytest.mark.xfail( |
596 |
| - condition=condition, |
597 |
| - raises=(NotImplementedError, RuntimeError), |
598 |
| - reason=f"Not supported{message}", |
| 602 | + # Create a MarkDecorator and store as an attribute of "wrapper" |
| 603 | + setattr( |
| 604 | + wrapper, |
| 605 | + "minimum_version", |
| 606 | + pytest.mark.xfail( |
| 607 | + condition=condition, |
| 608 | + raises=( |
| 609 | + NotImplementedError, # Raised directly, above |
| 610 | + AssertionError, # e.g. through CliRunner.assert_exit_0() |
| 611 | + RuntimeError, # e.g. through genno.Computer |
| 612 | + ) |
| 613 | + + tuple(raises or ()), # Other exception classes |
| 614 | + reason=f"Not supported{message}", |
| 615 | + ), |
599 | 616 | )
|
600 |
| - |
601 |
| - # Attach to the test function |
602 |
| - try: |
603 |
| - test_func.pytestmark.append(mark) |
604 |
| - except AttributeError: |
605 |
| - test_func.pytestmark = [mark] |
606 |
| - |
607 |
| - return test_func |
608 |
| - |
609 |
| - # Store the decorator on the wrapped function |
610 |
| - setattr(wrapper, "minimum_version", marker) |
| 617 | + except ImportError: |
| 618 | + pass # Pytest not present; testing is not happening |
611 | 619 |
|
612 | 620 | return wrapper
|
613 | 621 |
|
|
0 commit comments