forked from smarie/python-pytest-cases
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfixture__creation.py
More file actions
126 lines (100 loc) · 4.45 KB
/
Copy pathfixture__creation.py
File metadata and controls
126 lines (100 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# Authors: Sylvain MARIE <sylvain.marie@se.com>
# + All contributors to <https://github.com/smarie/python-pytest-cases>
#
# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
from __future__ import division
from inspect import getmodule, currentframe
from warnings import warn
try:
# type hints, python 3+
from typing import Callable, Any, Union, Iterable # noqa
from types import ModuleType # noqa
except ImportError:
pass
from .common_others import make_identifier
class ExistingFixtureNameError(ValueError):
"""
Raised by `add_fixture_to_callers_module` when a fixture already exists in a module
"""
def __init__(self, module, name, caller):
self.module = module
self.name = name
self.caller = caller
def __str__(self):
return f"Symbol `{self.name}` already exists in module {self.module} and therefore a corresponding " \
f"fixture can not be created by `{self.caller}`"
RAISE = 0
WARN = 1
CHANGE = 2
def check_name_available(module,
name, # type: str
if_name_exists=RAISE, # type: int
name_changer=None, # type: Callable
caller=None, # type: Callable[[Any], Any]
extra_forbidden_names=() # type: Iterable[str]
):
"""
Routine to check that a name is not already in dir(module) + extra_forbidden_names.
The `if_name_exists` argument allows users to specify what happens if a name exists already.
`if_name_exists=CHANGE` allows users to ask for a new non-conflicting name to be found and returned.
:param module: a module or a class. dir(module) + extra_forbidden_names is used as a reference of forbidden names
:param name: proposed name, to check against existent names in module
:param if_name_exists: policy to apply if name already exists in dir(module) + extra_forbidden_names
:param name_changer: an optional custom name changer function for new names to be generated
:param caller: for warning / error messages. Something identifying the caller
:param extra_forbidden_names: a reference list of additional forbidden names that can be provided, in addition to
dir(module)
:return: a name that might be different if policy was CHANGE
"""
new_name = make_identifier(name)
if new_name != name:
if if_name_exists is RAISE:
raise ValueError(f"Proposed name is an invalid identifier: {name}")
elif if_name_exists is WARN:
warn(f"{name} name was not a valid identifier, changed it to {new_name}")
name = new_name
if name_changer is None:
# default style for name changing. i starts with 1
def name_changer(name, i):
return f'{name}_{i}'
ref_list = dir(module) + list(extra_forbidden_names)
if name in ref_list:
if caller is None:
caller = ''
# Name already exists: act according to policy
if if_name_exists is RAISE:
raise ExistingFixtureNameError(module, name, caller)
elif if_name_exists is WARN:
warn(f"{caller} Overriding symbol {name} in module {module}")
elif if_name_exists is CHANGE:
# find a non-used name in that module
i = 1
name2 = name_changer(name, i)
while name2 in ref_list:
i += 1
name2 = name_changer(name, i)
name = name2
else:
raise ValueError(f"invalid value for `if_name_exists`: {if_name_exists}")
return name
def get_caller_module(frame_offset=1):
# type: (...) -> ModuleType
""" Return the module where the last frame belongs.
:param frame_offset: an alternate offset to look further up in the call stack
:return:
"""
# grab context from the caller frame
frame = _get_callerframe(offset=frame_offset)
return getmodule(frame)
def _get_callerframe(offset=0):
""" Return a frame in the call stack
:param offset: an alternate offset to look further up in the call stack
:return:
"""
# inspect.stack is extremely slow, the fastest is sys._getframe or inspect.currentframe().
# See https://gist.github.com/JettJones/c236494013f22723c1822126df944b12
# frame = sys._getframe(2 + offset)
frame = currentframe()
for _ in range(2 + offset):
frame = frame.f_back
return frame