Coverage for src/pytest_cases/fixture__creation.py: 81%
53 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-09-26 21:52 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-09-26 21:52 +0000
1# Authors: Sylvain MARIE <sylvain.marie@se.com>
2# + All contributors to <https://github.com/smarie/python-pytest-cases>
3#
4# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
5from __future__ import division
7from inspect import getmodule, currentframe
8from warnings import warn
10try:
11 # type hints, python 3+
12 from typing import Callable, Any, Union, Iterable # noqa
13 from types import ModuleType # noqa
14except ImportError:
15 pass
17from .common_others import make_identifier
20class ExistingFixtureNameError(ValueError):
21 """
22 Raised by `add_fixture_to_callers_module` when a fixture already exists in a module
23 """
24 def __init__(self, module, name, caller):
25 self.module = module
26 self.name = name
27 self.caller = caller
29 def __str__(self):
30 return "Symbol `%s` already exists in module %s and therefore a corresponding fixture can not be created by " \
31 "`%s`" % (self.name, self.module, self.caller)
34RAISE = 0
35WARN = 1
36CHANGE = 2
39def check_name_available(module,
40 name, # type: str
41 if_name_exists=RAISE, # type: int
42 name_changer=None, # type: Callable
43 caller=None, # type: Callable[[Any], Any]
44 extra_forbidden_names=() # type: Iterable[str]
45 ):
46 """
47 Routine to check that a name is not already in dir(module) + extra_forbidden_names.
48 The `if_name_exists` argument allows users to specify what happens if a name exists already.
50 `if_name_exists=CHANGE` allows users to ask for a new non-conflicting name to be found and returned.
52 :param module: a module or a class. dir(module) + extra_forbidden_names is used as a reference of forbidden names
53 :param name: proposed name, to check against existent names in module
54 :param if_name_exists: policy to apply if name already exists in dir(module) + extra_forbidden_names
55 :param name_changer: an optional custom name changer function for new names to be generated
56 :param caller: for warning / error messages. Something identifying the caller
57 :param extra_forbidden_names: a reference list of additional forbidden names that can be provided, in addition to
58 dir(module)
59 :return: a name that might be different if policy was CHANGE
60 """
61 new_name = make_identifier(name)
62 if new_name != name:
63 if if_name_exists is RAISE: 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true
64 raise ValueError("Proposed name is an invalid identifier: %s" % name)
65 elif if_name_exists is WARN: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true
66 warn("%s name was not a valid identifier, changed it to %s" % (name, new_name))
67 name = new_name
69 if name_changer is None:
70 # default style for name changing. i starts with 1
71 def name_changer(name, i):
72 return name + '_%s' % i
74 ref_list = dir(module) + list(extra_forbidden_names)
76 if name in ref_list:
77 if caller is None:
78 caller = ''
80 # Name already exists: act according to policy
81 if if_name_exists is RAISE: 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true
82 raise ExistingFixtureNameError(module, name, caller)
84 elif if_name_exists is WARN: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true
85 warn("%s Overriding symbol %s in module %s" % (caller, name, module))
87 elif if_name_exists is CHANGE: 87 ↛ 97line 87 didn't jump to line 97, because the condition on line 87 was never false
88 # find a non-used name in that module
89 i = 1
90 name2 = name_changer(name, i)
91 while name2 in ref_list:
92 i += 1
93 name2 = name_changer(name, i)
95 name = name2
96 else:
97 raise ValueError("invalid value for `if_name_exists`: %s" % if_name_exists)
99 return name
102def get_caller_module(frame_offset=1):
103 # type: (...) -> ModuleType
104 """ Return the module where the last frame belongs.
106 :param frame_offset: an alternate offset to look further up in the call stack
107 :return:
108 """
109 # grab context from the caller frame
110 frame = _get_callerframe(offset=frame_offset)
111 return getmodule(frame)
114def _get_callerframe(offset=0):
115 """ Return a frame in the call stack
117 :param offset: an alternate offset to look further up in the call stack
118 :return:
119 """
120 # inspect.stack is extremely slow, the fastest is sys._getframe or inspect.currentframe().
121 # See https://gist.github.com/JettJones/c236494013f22723c1822126df944b12
122 # frame = sys._getframe(2 + offset)
123 frame = currentframe()
124 for _ in range(2 + offset):
125 frame = frame.f_back
126 return frame