diff --git a/docs/api_reference.md b/docs/api_reference.md index 3a2f5db8..a8800ddf 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -51,6 +51,57 @@ def case_hi(): - `marks`: optional pytest marks to add on the case. Note that decorating the function directly with the mark also works, and if marks are provided in both places they are merged. +### `@with_case_tags` + +```python +@with_case_tags(*tags, # type: Any + ): +``` + +This decorator can be applied to a class defining cases to apply multiple +`*tags` to all case methods defined thereby. + +```python +@with_case_tags('tag_1', 'tag_2') +class CasesContainerClass: + + def case_one(self, ...): + ... + + @case(tags='another_tag') + def case_two(self, ...): + ... + + @case(tags='tag_1') + def case_three(self, ...): + ... +``` + +This is equivalent to: + + +```python +class CasesContainerClass: + + @case(tags=('tag_1', 'tag_2')) + def case_one(self, ...): + ... + + @case(tags=('another_tag', 'tag_1', 'tag_2')) + def case_two(self, ...): + ... + + @case(tags=('tag_1', 'tag_2')) + def case_three(self, ...): + ... +``` + +**Parameters:** + + - `tags`: custom tags to be added to all case methods. See also [`@case(tags=...)`](#case). + + + ### `copy_case_info` ```python diff --git a/docs/changelog.md b/docs/changelog.md index cfda97a7..b00ef711 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,12 @@ # Changelog +### 3.10.0 - (in progress) New `with_case_tags` decorator + +- Added the `with_case_tags` decorator for applying common tags to all cases + defined in a case class. Fixes [#351](https://github.com/smarie/python-pytest-cases/issues/351). + PR [#361](https://github.com/smarie/python-pytest-cases/pull/361) + by [@michele-riva](https://github.com/michele-riva). + ### 3.9.1 - support for python 3.14 and pytest 8.4 - Fixed `AttributeError: 'MiniMetafunc' object has no attribute '_params_directness'` when a case function is @@ -12,6 +19,10 @@ [#186](https://github.com/smarie/python-pytest-cases/issues/186) - Fixed test suite for python 3.14, officializing the support for this version. +### 3.9.0 - yanked version + +This version was yanked. See 3.9.1. + ### 3.8.6 - compatibility fix - Fixed issue with legacy python 2.7 and 3.5. Fixes [#352](https://github.com/smarie/python-pytest-cases/issues/352). diff --git a/docs/index.md b/docs/index.md index 420ad927..c6f9de23 100644 --- a/docs/index.md +++ b/docs/index.md @@ -269,7 +269,7 @@ def test_bad_datasets(data, err_type, err_msg): ``` - - the `has_tag` argument allows you to filter cases based on tags set on case functions using the `@case` decorator. See API reference of [`@case`](./api_reference.md#case) and [`@parametrize_with_cases`](./api_reference.md#parametrize_with_cases). + - the `has_tag` argument allows you to filter cases based on tags set on case functions using the `@case` decorator. See API reference of [`@case`](./api_reference.md#case) and [`@parametrize_with_cases`](./api_reference.md#parametrize_with_cases). Tags shared by multiple cases grouped inside a class may be added automatically to all cases using the [`@with_case_tags`](./api_reference.md#with_case_tags) decorator. ```python diff --git a/src/pytest_cases/__init__.py b/src/pytest_cases/__init__.py index 80416ddc..675c9af6 100644 --- a/src/pytest_cases/__init__.py +++ b/src/pytest_cases/__init__.py @@ -10,7 +10,7 @@ from .fixture_parametrize_plus import pytest_parametrize_plus, parametrize_plus, parametrize, fixture_ref from .case_funcs import case, copy_case_info, set_case_id, get_case_id, get_case_marks, \ - get_case_tags, matches_tag_query, is_case_class, is_case_function + get_case_tags, matches_tag_query, is_case_class, is_case_function, with_case_tags from .case_parametrizer_new import parametrize_with_cases, THIS_MODULE, get_all_cases, get_parametrize_args, \ get_current_case_id, get_current_cases, get_current_params, CasesCollectionWarning @@ -53,6 +53,7 @@ # case functions 'case', 'copy_case_info', 'set_case_id', 'get_case_id', 'get_case_marks', 'get_case_tags', 'matches_tag_query', 'is_case_class', 'is_case_function', + 'with_case_tags', # test functions 'get_all_cases', 'parametrize_with_cases', 'THIS_MODULE', 'get_parametrize_args', 'get_current_case_id', 'get_current_cases', 'get_current_params', 'CasesCollectionWarning' diff --git a/src/pytest_cases/case_funcs.py b/src/pytest_cases/case_funcs.py index 02dbcafb..54a1ae59 100644 --- a/src/pytest_cases/case_funcs.py +++ b/src/pytest_cases/case_funcs.py @@ -366,3 +366,35 @@ def is_case_function(f, # type: Any except: # GH#287: safe fallback return False + + +def with_case_tags(*tags): + """Attach `tags` to all cases defined in the decorated class.""" + def _decorator(cls): + if is_case_function(cls): + raise ValueError( + 'Cannot use `with_case_tags` on a case ' + 'function. Use the `@case` decorator instead.' + ) + if not is_case_class(cls): + raise ValueError('`with_case_tags` can only be applied to classes ' + 'defining a collection of cases.') + for case_name in dir(cls): + case_ = getattr(cls, case_name) + if not is_case_function(case_): # Not a case + continue + try: + case_info = getattr(case_, CASE_FIELD) + except AttributeError: + # Not explicitly decorated with @case. Do so now. + # NB: `case(obj) is obj`, i.e., the `@case` decorator + # only adds some attributes to `obj`. In the future, if + # `@case` will return a different object, we will have + # to `setattr(cls, case_name, case_mod)` + _ = case(case_) + case_info = getattr(case_, CASE_FIELD) + tags_to_add = tuple(t for t in tags if t not in case_info.tags) + case_info.add_tags(tags_to_add) + return cls + return _decorator +