Skip to content

Add with_case_tags decorator #361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
51 changes: 51 additions & 0 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

### 3.9.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.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).
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/pytest_cases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'
Expand Down
32 changes: 32 additions & 0 deletions src/pytest_cases/case_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Loading