|
3 | 3 | import dis
|
4 | 4 | import sys
|
5 | 5 | import warnings
|
6 |
| -from typing import Union, Tuple, Any, Optional |
| 6 | +from typing import Callable, Union, Tuple, Any, Optional |
7 | 7 | from types import FrameType, CodeType
|
8 | 8 | from collections import namedtuple as standard_namedtuple
|
| 9 | +from functools import wraps |
9 | 10 | from functools import lru_cache
|
10 | 11 |
|
11 | 12 | import executing
|
12 | 13 |
|
13 |
| -__version__ = "0.5.4" |
| 14 | +__version__ = "0.5.5" |
14 | 15 | __all__ = [
|
15 |
| - "VarnameRetrievingError", "varname", "will", |
| 16 | + "VarnameRetrievingError", "varname", "will", "inject_varname", |
16 | 17 | "inject", "nameof", "namedtuple", "Wrapper", "debug"
|
17 | 18 | ]
|
18 | 19 |
|
@@ -151,6 +152,62 @@ def will(caller: int = 1, raise_exc: bool = True) -> Optional[str]:
|
151 | 152 | # ast.Attribute
|
152 | 153 | return node.attr
|
153 | 154 |
|
| 155 | +def inject_varname( |
| 156 | + cls: type = None, *, |
| 157 | + caller: int = 1, |
| 158 | + multi_vars: bool = False, |
| 159 | + raise_exc: bool = True |
| 160 | +) -> Union[type, Callable[[type], type]]: |
| 161 | + """A decorator to inject __varname__ attribute to a class |
| 162 | +
|
| 163 | + Args: |
| 164 | + caller: The call stack index, indicating where this class |
| 165 | + is instantiated relative to where the variable is finally retrieved |
| 166 | + multi_vars: Whether allow multiple variables on left-hand side (LHS). |
| 167 | + If `True`, this function returns a tuple of the variable names, |
| 168 | + even there is only one variable on LHS. |
| 169 | + If `False`, and multiple variables on LHS, a |
| 170 | + `VarnameRetrievingError` will be raised. |
| 171 | + raise_exc: Whether we should raise an exception if failed |
| 172 | + to retrieve the name. |
| 173 | +
|
| 174 | + Examples: |
| 175 | + >>> @inject_varname |
| 176 | + >>> class Foo: pass |
| 177 | + >>> foo = Foo() |
| 178 | + >>> # foo.__varname__ == 'foo' |
| 179 | +
|
| 180 | + Returns: |
| 181 | + The wrapper function or the class itself if it is specified explictly. |
| 182 | + """ |
| 183 | + if cls is not None: |
| 184 | + # Used as @inject_varname directly |
| 185 | + return inject_varname( |
| 186 | + caller=caller, |
| 187 | + multi_vars=multi_vars, |
| 188 | + raise_exc=raise_exc |
| 189 | + )(cls) |
| 190 | + |
| 191 | + # Used as @inject_varname(multi_vars=..., raise_exc=...) |
| 192 | + def wrapper(cls): |
| 193 | + """The wrapper function to wrap a class and inject `__varname__`""" |
| 194 | + orig_init = cls.__init__ |
| 195 | + |
| 196 | + @wraps(cls.__init__) |
| 197 | + def wrapped_init(self, *args, **kwargs): |
| 198 | + """Wrapped init function to replace the original one""" |
| 199 | + self.__varname__ = varname( |
| 200 | + caller=caller, |
| 201 | + multi_vars=multi_vars, |
| 202 | + raise_exc=raise_exc |
| 203 | + ) |
| 204 | + orig_init(self, *args, **kwargs) |
| 205 | + |
| 206 | + cls.__init__ = wrapped_init |
| 207 | + return cls |
| 208 | + |
| 209 | + return wrapper |
| 210 | + |
154 | 211 | def inject(obj: object) -> object:
|
155 | 212 | """Inject attribute `__varname__` to an object
|
156 | 213 |
|
@@ -181,6 +238,9 @@ def inject(obj: object) -> object:
|
181 | 238 | Returns:
|
182 | 239 | The object with __varname__ injected
|
183 | 240 | """
|
| 241 | + warnings.warn("Function inject will be removed in 0.6.0. Use " |
| 242 | + "varname.inject_varname to decorate your class.", |
| 243 | + DeprecationWarning) |
184 | 244 | vname = varname()
|
185 | 245 | try:
|
186 | 246 | setattr(obj, '__varname__', vname)
|
|
0 commit comments