A simple folder structure shown below, and all the functions in test.py have keyword decorator on it.
lib
|--- keyword.py
main
|--- test.py
Keyword.py
from functools import wraps
def keyword(name=None, tags=(), types=()):
def _method_wrapper(func):
@wraps(func)
def _passargs(self, *args, **kwargs):
print(func.__code__.co_filename) # --> '..\main\test.py'
return func(self, *args, **kwargs)
print(_passargs.__code__.co_filename) # --> '..\lib\keyword.py'
return _passargs
return _method_wrapper
notes: print(..)
is just an example, I need both _passargs
& func
to have the same code object, instead of printing another variable :)
As you can see, the _passargs
got the wrong co_filename
.
This is from robotframework
keyword.py, and I modified it for my own purpose. However I failed to figure out how to make both _passargs
& func
have the correct source file location so the robot.libdoc
can generate doc.libspec correctly.
Can anyone help?
The expectation
func.__code__.co_filename = '..\main\test.py'
_passargs.__code__.co_filename = '..\main\test.py'
Python version = 3.8.10
Answering my own question here to help ppl who may struggle with the same issue.
The original decorator func:
from functools import wraps
def keyword(name=None, tags=(), types=()):
def _method_wrapper(func):
@wraps(func)
def _passargs(self, *args, **kwargs):
print(func.__code__.co_filename) # --> '..\main\test.py'
return func(self, *args, **kwargs)
print(_passargs.__code__.co_filename) # --> '..\lib\keyword.py'
return _passargs
return _method_wrapper
Most of the time, we only needs the last level of decorator (return func(self, *args, **kwargs)
in this example) to return the proper attributes of the wrapped func.
However, in my case, there's a function which will scan all the functions with this keyword
decorator during initializing class. And it won't go into def _passargs
level due to we didn't provide any arguments but just trying to get the wrapped func (return _passargs
) related attributes.
To resolve this issue, we can only override the co_filename
at RUNTIME.
Here's the solution to override __code__
object in decorator.
from types import CodeType
from functools import wraps
def keyword(name=None, tags=(), types=()):
def _method_wrapper(func):
@wraps(func)
def _passargs(self, *args, **kwargs):
print(func.__code__.co_filename) # --> '..\main\test.py'
return func(self, *args, **kwargs)
fix_co_filename(_passargs, func.__code__.co_filename)
print(_passargs.__code__.co_filename) # --> '..\main\test.py'
return _passargs
return _method_wrapper
def fix_co_filename(func, co_filename):
fn_code = func.__code__
func.__code__ = CodeType(
fn_code.co_argcount,
fn_code.co_posonlyargcount,
fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
fn_code.co_code,
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars)