Python decorator to disable a certain function crashing - python

I have a function in python that's used in many views. Specifically its in a django app running under uwsgi. The function just fires tracking data into our database. I wanted to create a decorator that would disable that function for a specific call to the view that contains the function. Essentially:
#disable tracking
def view(request):
track(request) //disabled by decorator
The decorator works by replacing the global definition of track with a void function that does nothing. Since we're running this under uwsgi which is multithreaded if I replace a global definition it will replace the function for all threads running under the process, so I defined the decorator to only activate if the tid and pid are equivalent. Here:
def disable_tracking(func):
#decorator
def inner(*args, **kwargs):
original_tracker = pascalservice.track.track
anon = lambda *args, **kwargs: None
tid = lambda : str(current_thread().ident)
pid = lambda : str(getpid())
uid = lambda : tid() + pid()
current_uid = uid()
cache.set(current_uid, True)
switcher = lambda *args, **kwargs: anon(*args, **kwargs) if cache.get(uid()) else original_tracker(*args, **kwargs)
pascalservice.track.track = switcher
result = func(*args, **kwargs)
cache.delete(current_uid)
pascalservice.track.track = original_tracker
return result
return inner
The wierd thing about this decorated function is that I'm getting occasional crashes and I want to verify if this style of coding is correct as it's a little unconventional.

What you are doing is called monkey patching. While not a totally bad practice it often leads to difficult to pinpoint bugs so use it with caution.
If the decorator is mandatory for some reason I would suggest adding some flag to the request object in your decorator and add a check for that flag in your track function.
The decorator :
def disable_tracking(func):
def wrapper(*args, **kwargs):
kwargs["request"].pascalservice_do_not_track = true
return func(*args, **kwargs)
return wrapper
Beginning of track function :
if hasattr(request, "pascalservice_do_not_track"):
return
# do the tracking ...
You may also just comment the line calling track in your view.

Related

Python decorator that can be also used as stand-alone function

I'm quite new to decorators and I'm trying to build a decorator with an argument that should work both as a decorator and a stand-alone function. The basic idea is to raise an error if some condition is not satisfied. Example:
ensure_condition("fail") # exception should be raised
ensure_condition("pass") # nothing should happen
#ensure_condition("fail") # check condition before every `func` call
def f1():
return 1
I thought about doing this:
def ensure_condition(arg: str):
if not _validate(arg):
raise Exception("failed")
def ensure_condition_decorator(f = lambda *_: None):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
return ensure_condition_decorator
But the above results in the _validate function being called also when the f1 function is declared (not only executed).
Any other ideas?
Thanks!

How to discard superfluous parameters when calling a function?

Background
I'm working on a PyQt application and have added the following debug decorator:
def debug_decorator(func):
#functools.wraps(func)
def wrapper_func(*args, **kwargs):
logging.debug(f"Calling {func.__name__}")
ret_val = func(*args, **kwargs)
logging.debug(f"{func.__name__} returned {ret_val!r}")
return ret_val
return wrapper_func
Among other things, i use this for click handler functions, for example:
self.share_qpb = QtWidgets.QPushButton("Share")
self.share_qpb.clicked.connect(self.on_share_clicked)
[...]
#debug_decorator
def on_share_clicked(self):
If i run this code i get the following error when the share button is clicked:
TypeError: on_share_clicked() takes 1 positional argument but 2 were given
The reason for this is that the clicked signal in Qt/PyQt sends the checked state of the button (documentation)
This is only an issue when i decorate functions with my debug decorator, not otherwise (i guess the extra argument is discarded somewhere)
Question
How can i make my debug decorator work without having to add a parameter for every clicked signal handler?
(I'd like to find a solution which works both for cases when the arguments match and when there is one extra argument)
I'm running Python 3.8 and PyQt 5.15
I managed to solve this by checking to see if the number of arguments provided len(args) is more than the number of arguments that the decorated function accepts func.__code__.co_argcount
I also check if the name of the decorated function contains the string "clicked" since i have made a habit of having that as part of the name of my click handler functions. This has the advantage that if another decorated function is called with too many arguments we will (as usual) get an error
def debug_decorator(func):
#functools.wraps(func)
def wrapper_func(*args, **kwargs):
logging.debug(f"=== {func.__name__} was called")
func_arg_count_int = func.__code__.co_argcount
func_name_str = func.__name__
if len(args) > func_arg_count_int and "clicked" in func_name_str: # <----
args = args[:-1]
ret_val = func(*args, **kwargs)
logging.debug(f"=== {func.__name__} returned {ret_val!r}")
return ret_val
return wrapper_func
Thank you to #ekhumoro for helping me out with this!

Avoiding double wrapping in a decorator

I have a Python 3 script similar to the following:
def transaction(write=False):
def _transaction_deco(fn):
#wraps(fn)
def _wrapper(*args, **kwargs):
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
ret = fn(*args, **kwargs)
delattr(env, 'timestamp')
return ret
return _wrapper
return _transaction_deco
#transaction(True)
def fn1(x=False):
if x:
return fn2()
do_something()
#transaction(True)
def fn2():
do_something_else()
fn1 and fn2 can be called independently, and both are wrapped in the transaction decorator.
Also, fn1 can call fn2 under certain circumstances. This is where I have a problem, because the decorator code is called twice, transactions are nested, and the timestamp is set twice and deleted twice (actually, the second time delattr(env, 'timestamp') is called, an exception is raised because timestamp is no longer there).
I want whatever is decorated with transaction to only be decorated if not already so.
I tried to modify my code based on hints from a separate Q&A:
def transaction(write=False):
def _transaction_deco(fn):
# Avoid double wrap.
if getattr(fn, 'in_txn', False):
return fn(*args, **kwargs)
#wraps(fn)
def _wrapper(*args, **kwargs):
# Mark transaction begin timestamp.
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
ret = fn(*args, **kwargs)
delattr(env, 'timestamp')
return ret
_wrapper.in_txn = True
return _wrapper
return _transaction_deco
I.e. if the _in_txn attribute is set for the function, return the bare function because we are already in a transaction.
However, if I run fn1(True) with a debugger, I see that the check for in_txn is never true because the _wrapper variable is set to f1 for the outer call, and to f2 for the inner call, so the functions are being both decorated separately.
I could set the timestamp variables as properties of the context manager, but I still end up with two bested context managers, and if both trnsactions are R/W, Bad Things will happen.
Can someone suggest a better solution?
Edit: Based on the replied received, I believe I omitted a key element: fn1 and fn2 are API metods and must always run within a transaction. The reason for using a decorator is to prevent the API implementer from having to make a decision about the transaction, or having to wrap the methods manually in a context manager or a decorator. However I would be in favor of a non-decorator approach as long as it keeps the implementation of fn1 and fn2 plain.
Rather than a decorator, I'd just make another context manager to use explicitly.
#contextlib.contextmanager
def MyTxnManager(write):
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
yield txn
delattr(env, 'timestamp')
with MyTxnManager(True) as txn:
fn1()
Here, instead of hiding the transaction inside fn1 and fn2, we wrap just the timestamp management inside a new transaction manager. This way, calls to fn1 and fn2 occur in a transaction only if we explicitly cause them to do so.
There's no need to handle this in the decorator. It's much simpler to separate out the wrapped and unwrapped versions of fn2, e.g.:
def _fn2():
do_something_else()
#transaction(True)
def fn1(x=False):
if x:
return _fn2()
do_something()
fn2 = transaction(True)(_fn2)
The decorator syntax is just syntactic sugar, so the result will be exactly the same.
You can always just check if the environment has a timestamp already and not create it in that case. It is using the already-existing attribute as a flag instead of making a new one:
def transaction(write=False):
def _transaction_deco(fn):
#wraps(fn)
def _wrapper(*args, **kwargs):
if hasattr(env, 'timestamp'):
ret = fn(*args, **kwargs)
else:
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
ret = fn(*args, **kwargs)
delattr(env, 'timestamp')
return ret
return _wrapper
return _transaction_deco
This will let you nest an arbitrary number of transacted calls, and only use the timestamp and context manager of the outermost one.

variables as decorator arguments

I have found out that decorator arguments are passed at decorator definition rather than invocation like with functions.
Now I wonder if it is possible to make the decorater get the value of a variable at runtime like this, the decorater should print the current value of state instead of the one it head at definition:
def deco(msg):
def decorater(func):
def wrapper(*args, **kwargs):
print msg
func(*args, **kwargs)
return wrapper
return decorater
def func():
local = {
"state": None
}
#deco(local["state"])
def test():
pass
def setState(newState):
local["state"] = newState
setState("start")
test()
setState("test")
test()
func()
In your example, deco() is a decorator factory; you're creating the decorator which will then immediately be invoked. More generally, you invoke a decorator at the time that you're defining the function that you're decorating.
You can do what you're trying to do with minimal changes by just not passing in state, and accessing it as a global from within wrapper(), in which case you don't need deco(); you could just use #decorator directly. That said, I think there are better ways to do what you're trying to do.
John you should read this. In python, the variable is not the object. You question, is it "possible to make the decorator get the value of a variable at runtime", doesn't make sense because of python's scoping rules. The decorator function does not generally have access to the scope where state is defined. There are several ways you could get the behavior you want though.
Without knowing the specifics of what you're trying to do, here are two that might work. The first uses closure:
state = None
def with_closure(f):
def helper(*args, **kwargs):
# state is in scope for this function
print "Current state is: {}.".format(state)
return f(*args, **kwargs)
return helper
#with_closure
def foo():
return "something"
Or you could make an object to keep track of state:
class StateHolder:
def set_state(self, state):
self.state = state
def with_state_object(state_object):
def decorator(f):
def helper(*args, **kwargs):
print "Current state is: {}.".format(state_object.state)
return f(*args, **kwargs)
return helper
return decorator
global_state = StateHolder()
global_state.set_state("some state")
#with_state_object(global_state)
def foo():
return "something"

Preventing function (or decorator) from being nested

I've got some code in a decorator that I only want run once. Many other functions (utility and otherwise) will be called later down the line, and I want to ensure that other functions that may have this decorator aren't accidentally used way down in the nest of function calls.
I also want to be able to check, at any point, whether or not the current code has been wrapped in the decorator or not.
I've written this, but I just wanted to see if anyone else can think of a better/more elegant solution than checking for the (hopefully!) unique function name in the stack.
import inspect
def my_special_wrapper(fn):
def my_special_wrapper(*args, **kwargs):
""" Do some magic, only once! """
# Check we've not done this before
for frame in inspect.stack()[1:]: # get stack, ignoring current!
if frame[3] == 'my_special_wrapper':
raise StandardError('Special wrapper cannot be nested')
# Do magic then call fn
# ...
fn(*args, **kwargs)
return my_special_wrapper
def within_special_wrapper():
""" Helper to check that the function has been specially wrapped """
for frame in inspect.stack():
if frame[3] == 'my_special_wrapper':
return True
return False
#my_special_wrapper
def foo():
print within_special_wrapper()
bar()
print 'Success!'
#my_special_wrapper
def bar():
pass
foo()
Here is an example of using a global for this task - in what I believe is a relatively safe way:
from contextlib import contextmanager
from functools import wraps
_within_special_context = False
#contextmanager
def flag():
global _within_special_context
_within_special_context = True
try:
yield
finally:
_within_special_context = False
#I'd argue this would be best replaced by just checking the variable, but
#included for completeness.
def within_special_wrapper():
return _within_special_context
def my_special_wrapper(f):
#wraps(f)
def internal(*args, **kwargs):
if not _within_special_context:
with flag():
...
f(*args, **kwargs)
else:
raise Exception("No nested calls!")
return internal
#my_special_wrapper
def foo():
print(within_special_wrapper())
bar()
print('Success!')
#my_special_wrapper
def bar():
pass
foo()
Which results in:
True
Traceback (most recent call last):
File "/Users/gareth/Development/so/test.py", line 39, in <module>
foo()
File "/Users/gareth/Development/so/test.py", line 24, in internal
f(*args, **kwargs)
File "/Users/gareth/Development/so/test.py", line 32, in foo
bar()
File "/Users/gareth/Development/so/test.py", line 26, in internal
raise Exception("No nested calls!")
Exception: No nested calls!
Using a context manager ensures that the variable is unset. You could just use try/finally, but if you want to modify the behaviour for different situations, the context manager can be made to be flexible and reusable.
The obvious solution is to have special_wrapper set a global flag, and just skip its magic if the flag is set.
This is about the only good use of a global variable - to allow a single piece of code to store information that is only used within that code, but which needs to survive the life of execution in that code.
It doesn't need to be set in global scope. The function could set the flag on itself, for example, or on any object or class, as long as nothing else will touch it.
As noted by Lattyware in comments, you'll want to use either a try/except, or perhaps even better, a context manager to ensure the variable is unset.
Update: If you need the wrapped code to be able to check if it is wrapped, then provide a function which returns the value of the flag. You might want to wrap it all up with a class for neatness.
Update 2: I see you're doing this for transaction management. There are probably already libraries which do this. I strongly recommend that you at least look at their code.
While my solution technically works, it requires a manual reset of the decorator, but you could very well modify things such that the outermost function is instead a class (with the instances being the wrappers of the decorated functions passed to it in __init__), and have reset() being called in __exit__(), which would then allow you to use the with statement to create the decorator to be usable only once within the context. Also note that it requires Python 3 due to the nonlocal keyword, but that can easily be adapted to 2.7 with a dict in place of the flag variable.
def once_usable(decorator):
"Apply this decorator function to the decorator you want to be usable only once until it is reset."
def outer_wrapper():
flag = False
def inner_wrapper(*args, **kwargs):
nonlocal flag
if not flag:
flag = True
return decorator(*args, **kwargs)
else:
print("Decorator currently unusable.") # raising an Error also works
def decorator_reset():
nonlocal flag
flag = False
return (inner_wrapper, decorator_reset)
return outer_wrapper()
Testing:
>>> def a(aa):
return aa*2
>>> def b(bb):
def wrapper(*args, **kwargs):
print("Decorated.")
return bb(*args, **kwargs)
return wrapper
>>> dec, reset = once_usable(b)
>>> aa = dec(a)
>>> aa(22)
Decorated.
44
>>> aaa = dec(a)
Decorator currently unusable.
>>> reset()
>>> aaa = dec(a)
>>> aaa(11)
Decorated.
22

Categories

Resources