Skip to content

Commit bc81e64

Browse files
authored
Merge pull request #155 from smarie/fix_issue_154
Work in progress: harmonize test ids across fixture unionsa and parametrize with fixture refs
2 parents 600b294 + de82d6e commit bc81e64

File tree

77 files changed

+2312
-1060
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+2312
-1060
lines changed

docs/api_reference.md

Lines changed: 163 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,152 @@ def case_hi():
3232
- `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.
3333

3434

35+
### `copy_case_info`
36+
37+
```python
38+
def copy_case_info(from_fun, # type: Callable
39+
to_fun # type: Callable
40+
):
41+
```
42+
43+
Copies all information from case function `from_fun` to `to_fun`.
44+
45+
46+
### `set_case_id`
47+
48+
```python
49+
def set_case_id(id, # type: str
50+
case_func # type: Callable
51+
):
52+
```
53+
54+
Sets an explicit id on case function `case_func`.
55+
56+
57+
### `get_case_id`
58+
59+
```python
60+
def get_case_id(case_func, # type: Callable
61+
prefix_for_default_ids='case_' # type: str
62+
):
63+
```
64+
65+
Return the case id associated with this case function.
66+
67+
If a custom id is not present, a case id is automatically created from the function name based on removing the provided prefix if present at the beginning of the function name. If the resulting case id is empty, "<empty_case_id>" will be returned.
68+
69+
70+
**Parameters:**
71+
72+
- `case_func`: the case function to get a case id for.
73+
74+
- `prefix_for_default_ids`: this prefix that will be removed if present on the function name to form the default case id.
75+
76+
77+
### `get_case_marks`
78+
79+
```python
80+
def get_case_marks(case_func, # type: Callable
81+
concatenate_with_fun_marks=False, # type: bool
82+
as_decorators=False # type: bool
83+
):
84+
```
85+
86+
Return the marks that are on the case function.
87+
88+
There are currently two ways to place a mark on a case function: either with `@pytest.mark.<name>` or in `@case(marks=...)`. This function returns a list of marks containing either both (if `concatenate_with_fun_marks` is `True`) or only the ones set with `@case` (`concatenate_with_fun_marks` is `False`, default).
89+
90+
**Parameters:**
91+
92+
- `case_func`: the case function
93+
94+
- `concatenate_with_fun_marks`: if `False` (default) only the marks declared in `@case` will be returned. Otherwise a concatenation of marks in `@case` and on the function (for example directly with `@pytest.mark.<mk>`) will be returned.
95+
96+
- `as_decorators`: when `True`, the marks (`MarkInfo`) will be transformed into `MarkDecorators` before being returned. Otherwise (default) the marks are returned as is.
97+
98+
99+
### `get_case_tags`
100+
101+
```python
102+
def get_case_tags(case_func # type: Callable
103+
):
104+
```
105+
106+
Return the tags on this case function or an empty tuple.
107+
108+
**Parameters:**
109+
110+
- `case_func`: the case function
111+
112+
113+
### `matches_tag_query`
114+
115+
```python
116+
def matches_tag_query(case_fun, # type: Callable
117+
has_tag=None, # type: Union[str, Iterable[str]]
118+
filter=None, # type: Union[Callable[[Callable], bool], Iterable[Callable[[Callable], bool]]] # noqa
119+
):
120+
```
121+
122+
This function is the one used by `@parametrize_with_cases` to filter the case functions collected. It can be used manually for tests/debug.
123+
124+
Returns True if the case function is selected by the query:
125+
126+
- if `has_tag` contains one or several tags, they should ALL be present in the tags set on `case_fun` (`get_case_tags`)
127+
128+
- if `filter` contains one or several filter callables, they are all called in sequence and the `case_fun` is only selected if ALL of them return a `True` truth value
129+
130+
**Parameters:**
131+
132+
- `case_fun`: the case function
133+
134+
- `has_tag`: one or several tags that should ALL be present in the tags set on `case_fun` for it to be selected.
135+
136+
- `filter`: one or several filter callables that will be called in sequence. If all of them return a `True` truth value, `case_fun` is selected.
137+
138+
139+
### `is_case_class`
140+
141+
```python
142+
def is_case_class(cls, # type: Any
143+
case_marker_in_name='Case', # type: str
144+
check_name=True # type: bool
145+
):
146+
```
147+
148+
This function is the one used by `@parametrize_with_cases` to collect cases within classes. It can be used manually for tests/debug.
149+
150+
Returns True if the given object is a class and, if `check_name=True` (default), if its name contains `case_marker_in_name`.
151+
152+
**Parameters:**
153+
154+
- `cls`: the object to check
155+
156+
- `case_marker_in_name`: the string that should be present in a class name so that it is selected. Default is 'Case'.
157+
158+
- `check_name`: a boolean (default True) to enforce that the name contains the word `case_marker_in_name`. If False, any class will lead to a `True` result whatever its name.
159+
160+
### `is_case_function`
161+
162+
```python
163+
def is_case_function(f, # type: Any
164+
prefix='case_', # type: str
165+
check_prefix=True # type: bool
166+
):
167+
```
168+
169+
This function is the one used by `@parametrize_with_cases` to collect cases. It can be used manually for tests/debug.
170+
171+
Returns True if the provided object is a function or callable and, if `check_prefix=True` (default), if it starts with `prefix`.
172+
173+
**Parameters:**
174+
175+
- `f`: the object to check
176+
177+
- `prefix`: the string that should be present at the beginning of a function name so that it is selected. Default is 'case_'.
178+
179+
- `check_prefix`: if this boolean is True (default), the prefix will be checked. If False, any function will lead to a `True` result whatever its name.
180+
35181
## 2 - Cases collection
36182

37183
### `@parametrize_with_cases`
@@ -40,13 +186,12 @@ def case_hi():
40186
@parametrize_with_cases(argnames: str,
41187
cases: Union[Callable, Type, ModuleRef] = AUTO,
42188
prefix: str = 'case_',
43-
glob:str = None,
44-
has_tag: Union[str, Iterable[str]]=None,
189+
glob: str = None,
190+
has_tag: Union[str, Iterable[str]] = None,
45191
filter: Callable = None,
46-
ids: Union[Callable, Iterable[str]]=None,
47-
idstyle: Optional[str]='explicit',
48-
idgen: Union[str, Callable]=_IDGEN,
49-
**kwargs
192+
ids: Union[Callable, Iterable[str]] = None,
193+
idstyle: Union[str, Callable] = None,
194+
scope: str = "function"
50195
)
51196
```
52197

@@ -79,13 +224,14 @@ argvalues = get_parametrize_args(host_class_or_module_of_f, cases_funs)
79224

80225
- `has_tag`: a single tag or a tuple, set, list of tags that should be matched by the ones set with the [`@case`](#case) decorator on the case function(s) to be selected.
81226

82-
- `filter`: a callable receiving the case function and returning True or a truth value in case the function needs to be selected.
83-
84-
- `ids`: same as in pytest.mark.parametrize. Note that an alternative way to create ids exists with `idgen`. Only one non-None `ids` or `idgen` should be provided. See [`@parametrize`](#parametrize) for details.
227+
- `filter`: a callable receiving the case function and returning `True` or a truth value in case the function needs to be selected.
85228

86-
- `idgen`: an id formatter. Either a string representing a template, or a callable receiving all argvalues at once (as opposed to the behaviour in pytest ids). This alternative way to generate ids can only be used when `ids` is not provided (None). You can use the special `pytest_cases.AUTO` formatter to generate an automatic id with template `<name>=<value>-<name2>=<value2>-...`. See [`@parametrize`](#parametrize) for details.
87-
88-
- `idstyle`: style of ids to be used in the "union" fixtures generated by [`@parametrize`](#parametrize) when some cases require fixtures. One of 'compact', 'explicit' or None/'nostyle'. See [`@parametrize`](#parametrize) for details.
229+
- `ids`: optional custom ids, similar to the one in `pytest.mark.parametrize`. Users may either provide an iterable of string ids, or a callable. If a callable is provided it will receive the case functions. Users may wish to use [`get_case_id`](#get_case_id) or other helpers in the [API](#1---case-functions) to inspect the case functions.
230+
231+
- `idstyle`: This is mostly for debug. Style of ids to be used in the "union" fixtures generated by [`@parametrize`](#parametrize) if some cases are transformed into fixtures behind the scenes. `idstyle` possible values are `'compact'`, `'explicit'` or `None`/`'nostyle'` (default), or a callable. `idstyle` has no effect if no cases are transformed into fixtures. As opposed to `ids`, a callable provided here will receive a `ParamAlternative` object indicating which generated fixture should be used. See [`@parametrize`](#parametrize) for details.
232+
233+
- `scope`: The scope of the union fixture to create if `fixture_ref`s are found in the argvalues
234+
89235

90236
### `get_all_cases`
91237

@@ -284,10 +430,11 @@ Identical to `param_fixtures` but for a single parameter name, so that you can a
284430
argvalues: Iterable[Any]=None,
285431
indirect: bool = False,
286432
ids: Union[Callable, Iterable[str]] = None,
287-
idstyle: Optional[str] = 'explicit',
433+
idstyle: Union[str, Callable] = None,
288434
idgen: Union[str, Callable] = _IDGEN,
289435
scope: str = None,
290436
hook: Callable = None,
437+
scope: str = "function",
291438
debug: bool = False,
292439
**args)
293440
```
@@ -322,15 +469,13 @@ Here as for all functions above, an optional `hook` can be passed, to apply on e
322469

323470
- `idgen`: an id formatter. Either a string representing a template, or a callable receiving all argvalues at once (as opposed to the behaviour in pytest ids). This alternative way to generate ids can only be used when `ids` is not provided (None). You can use the special `pytest_cases.AUTO` formatter to generate an automatic id with template `<name>=<value>-<name2>=<value2>-...`.
324471

325-
- `idstyle`: style of ids to be used in the "union" fixtures generated by `@parametrize` when some cases require fixtures. One of 'compact', 'explicit' or None/'nostyle'.
472+
- `idstyle`: This is mostly for debug. Style of ids to be used in the "union" fixtures generated by `@parametrize` if at least one `fixture_ref` is found in the argvalues. `idstyle` possible values are 'compact', 'explicit' or None/'nostyle' (default), or a callable. `idstyle` has no effect if no `fixture_ref` are present in the argvalues. As opposed to `ids`, a callable provided here will receive a `ParamAlternative` object indicating which generated fixture should be used.
326473

327-
- `scope`: same as in pytest.mark.parametrize
328-
474+
- `scope`: The scope of the union fixture to create if `fixture_ref`s are found in the argvalues. Otherwise same as in `pytest.mark.parametrize`.
475+
329476
- `hook`: an optional hook to apply to each fixture function that is created during this call. The hook function will be called everytime a fixture is about to be created. It will receive a single argument (the function implementing the fixture) and should return the function to use. For example you can use `saved_fixture` from `pytest-harvest` as a hook in order to save all such created fixtures in the fixture store.
330477

331478
- `debug`: print debug messages on stdout to analyze fixture creation (use pytest -s to see them)
332-
333-
- `args`: additional {argnames: argvalues} definition
334479

335480

336481
### `lazy_value`

docs/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
### 2.8.0 - harmonization of ids and public API for cases info
4+
5+
- Major refactoring of the way ids and marks are handled in `fixture_union`, `@parametrize` and `@parametrize_with_cases`. See [documentation](./index.md) for details. Fixed [#154](https://github.com/smarie/python-pytest-cases/issues/154)
6+
7+
- New public API to manipulate information about a case function: `copy_case_info`, `set_case_id`, `get_case_id`, `get_case_marks`,
8+
`get_case_tags`, `matches_tag_query`, `is_case_class`, `is_case_function`. See [API reference](./api_reference.md).
9+
310
### 2.7.2 - Bugfix with doctest
411

512
- Fixed `AttributeError: 'DoctestItem' object has no attribute '_request'` when executing doctests. Fixes [#156](https://github.com/smarie/python-pytest-cases/issues/156)

pytest_cases/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from .case_parametrizer_legacy import cases_data, CaseDataGetter, get_all_cases_legacy, \
1818
get_pytest_parametrize_args_legacy, cases_fixture
1919

20-
from .case_funcs_new import case, CaseInfo
20+
from .case_funcs_new import case, copy_case_info, set_case_id, get_case_id, get_case_marks, \
21+
get_case_tags, matches_tag_query, is_case_class, is_case_function
2122
from .case_parametrizer_new import parametrize_with_cases, THIS_MODULE, get_all_cases, get_parametrize_args
2223

2324
try:
@@ -58,9 +59,10 @@
5859
# V2 symbols
5960
'AUTO', 'AUTO2',
6061
# case functions
61-
'case', 'CaseInfo', 'get_all_cases',
62+
'case', 'copy_case_info', 'set_case_id', 'get_case_id', 'get_case_marks',
63+
'get_case_tags', 'matches_tag_query', 'is_case_class', 'is_case_function',
6264
# test functions
63-
'parametrize_with_cases', 'THIS_MODULE', 'get_parametrize_args'
65+
'get_all_cases', 'parametrize_with_cases', 'THIS_MODULE', 'get_parametrize_args'
6466
]
6567

6668
try: # python 3.5+ type hints

pytest_cases/case_funcs_legacy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
except ImportError:
2020
pass
2121

22-
from .case_funcs_new import CaseInfo
22+
from .case_funcs_new import case
2323

2424

2525
@function_decorator
@@ -61,7 +61,7 @@ def case_tags(*tags # type: Any
6161

6262
# we have to use "nested" mode for this decorator because in the decorator signature we have a var-positional
6363
def _apply(case_func):
64-
CaseInfo.get_from(case_func, create=True).add_tags(tags)
64+
case_func = case(tags=tags)(case_func)
6565
return case_func
6666

6767
return _apply

0 commit comments

Comments
 (0)