Python - Use decorated function in class - python

The parameters of my decorated function are getting swapped.
In authorized(self, resp), resp is becoming a ClientView object and self is becoming a resp variable.
How can I decorate this function so it can be used as a method?
It uses flask class view and flask_oauthlib.
Function code:
class ClientView(UserView):
#bp.route('/vklogin/authorized')
#vk.authorized_handler
def authorized(self, resp):
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
session['oauth_token'] = (resp['access_token'], '')
me = self.vk.get('method/users.get?uids={}'.format(resp['user_id']))
return '{}'.format(me.data)
Decorator function code:
class OAuthRemoteApp(object):
def authorized_handler(self, f):
#wraps(f)
def decorated(*args, **kwargs):
if 'oauth_verifier' in request.args:
try:
data = self.handle_oauth1_response()
except OAuthException as e:
data = e
elif 'code' in request.args:
try:
data = self.handle_oauth2_response()
except OAuthException as e:
data = e
else:
data = self.handle_unknown_response()
# free request token
session.pop('%s_oauthtok' % self.name, None)
session.pop('%s_oauthredir' % self.name, None)
return f(*((data,) + args), **kwargs)
return decorated

Consider this simplied (and runnable) version code of your code:
class OAuthRemoteApp(object):
def authorized_handler(self, f):
def decorated(*args, **kwargs):
print(self, f, args, kwargs) #1
# (<__main__.OAuthRemoteApp object at 0xb74d324c>, <function authorized at 0xb774ba04>, (<__main__.ClientView object at 0xb74d32cc>,), {})
data = 'resp'
print((data,) + args) #2
# ('resp', <__main__.ClientView object at 0xb74d32cc>)
return f(*((data,) + args), **kwargs)
return decorated
vk = OAuthRemoteApp()
class ClientView(object):
#vk.authorized_handler
def authorized(self, resp):
print(self, resp) #3
# ('resp', <__main__.ClientView object at 0xb7452eec>)
cv = ClientView()
cv.authorized()
Notice that the first item in args is the ClientView instance:
args = (<__main__.ClientView object at 0xb74d32cc>,)
The expression (data,) + args prepends 'resp' in front of the
ClientView instance. This is the source of the problem.
Notice that here self is resp and resp is the ClientView
instance. The arguments are being supplied in the wrong order.
One way to fix the problem is to insert data after the first item
in args:
class OAuthRemoteApp(object):
def authorized_handler(self, f):
def decorated(*args, **kwargs):
data = 'resp'
args = args[:1] + (data,) + args[1:]
return f(*args, **kwargs)
return decorated
vk = OAuthRemoteApp()
class ClientView(object):
#vk.authorized_handler
def authorized(self, resp):
print(self, resp)
cv = ClientView()
cv.authorized()
yields
(<__main__.ClientView object at 0xb7434f0c>, 'resp')
which shows the ClientView and resp arguments being supplied in the correct order.

Related

python decorator takes 1 positional argument but 5 were given after modifying for pytest

I'd appreciate some help with the following code, as I'm still relatively new to Python, and despite countless days trying to figure out where i'm going wrong, i cant seem to spot the error i'm making.
I've adapted the following code from an article on medium to create a logging decorator and then enhanced it to try and "redact pandas df and dictionary" from the logs. Using functools caused me a problem with pytest and pytest fixtures. A post on stack overflow suggested dropping functools in favour of decorators.
def log_decorator(_func=None):
def log_decorator_info(func):
def log_decorator_wrapper(*args, **kwargs):
_logger = Logger()
logger_obj = _logger.get_logger()
args_passed_in_function = args_excl_df_dict(*args)
kwargs_passed_in_function = kwargs_excl_df_dict(**kwargs)
formatted_arguments = join_args_kwargs(args_passed_in_function,kwargs_passed_in_function)
py_file_caller = getframeinfo(stack()[1][0])
extra_args = { 'func_name_override': func.__name__,'file_name_override': os.path.basename(py_file_caller.filename) }
""" Before to the function execution, log function details."""
logger_obj.info(f"Begin function - Arguments: {formatted_arguments}", extra=extra_args)
try:
""" log return value from the function """
args_returned_from_function = args_excl_df_dict(func(*args))
kwargs_returned_from_function = []
formatted_arguments = join_args_kwargs(args_returned_from_function,kwargs_returned_from_function)
logger_obj.info(f"End function - Returned: {formatted_arguments}", extra=extra_args)
except:
"""log exception if occurs in function"""
error_raised = str(sys.exc_info()[1])
logger_obj.error(f"Exception: {str(sys.exc_info()[1])}",extra=extra_args)
msg_to_send = f"{func.__name__} {error_raised}"
send_alert(APP_NAME,msg_to_send,'error')
raise
return func(*args, **kwargs)
return decorator.decorator(log_decorator_wrapper, func)
if _func is None:
return log_decorator_info
else:
return log_decorator_info(_func)
Having adapted the above code i cant figure out what is causing the following error
args_returned_from_function = args_excl_df_dict(func(*args))
TypeError: test_me() takes 4 positional arguments but 5 were given
Other functions which the log decorator relies on
def args_excl_df_dict(*args):
args_list = []
for a in args:
if isinstance(a,(pd.DataFrame,dict)):
a = 'redacted from log'
args_list.append(repr(a))
else:
args_list.append(repr(a))
return args_list
def kwargs_excl_df_dict(**kwargs):
kwargs_list = []
for k, v in kwargs.items():
if isinstance(v,(dict,pd.DataFrame)):
v = 'redacted from log'
kwargs_list.append(f"{k}={v!r}")
else:
kwargs_list.append(f"{k}={v!r}")
return kwargs_list
def join_args_kwargs(args,kwargs):
formatted_arguments = ", ".join(args + kwargs)
return str(formatted_arguments)
This is the code calling the decorator
#log_decorator.log_decorator()
def test_me(a, b, c, d):
return a, b
test_me(string, number, dictionary, pandas_df)
I think the problem is that the wrapper is including the function as an argument to the function.
Try adding this line and see if it helps
args = args[1:]
intor your log_decorator_wrapper function towards the top. Like this.
def log_decorator(_func=None):
def log_decorator_info(func):
def log_decorator_wrapper(*args, **kwargs):
args = args[1:] # < -------------------here
_logger = Logger()
logger_obj = _logger.get_logger()
args_passed_in_function = args_excl_df_dict(*args)
kwargs_passed_in_function = kwargs_excl_df_dict(**kwargs)
formatted_arguments = join_args_kwargs(args_passed_in_function,kwargs_passed_in_function)
py_file_caller = getframeinfo(stack()[1][0])
extra_args = { 'func_name_override': func.__name__,'file_name_override': os.path.basename(py_file_caller.filename) }
""" Before to the function execution, log function details."""
logger_obj.info(f"Begin function - Arguments: {formatted_arguments}", extra=extra_args)
try:
""" log return value from the function """
args_returned_from_function = args_excl_df_dict(func(*args))
kwargs_returned_from_function = []
formatted_arguments = join_args_kwargs(args_returned_from_function,kwargs_returned_from_function)
logger_obj.info(f"End function - Returned: {formatted_arguments}", extra=extra_args)
except:
"""log exception if occurs in function"""
error_raised = str(sys.exc_info()[1])
logger_obj.error(f"Exception: {str(sys.exc_info()[1])}",extra=extra_args)
msg_to_send = f"{func.__name__} {error_raised}"
send_alert(APP_NAME,msg_to_send,'error')
raise
return func(*args, **kwargs)
return decorator.decorator(log_decorator_wrapper, func)
if _func is None:
return log_decorator_info
else:
return log_decorator_info(_func)
If your code is as is in your editor, maybe look at the indentation on the first three functions. Then start from there to move down

AssertionError: expected view func if endpoint is not provided - python

trying to decorate end point with requires_fields decorator. this is the implementation.
import functools
from flask import request, jsonify
def requires_fields(fields):
required_fields = set(fields)
def wrapper(func):
#functools.wraps(func)
def decorated(*args, **kwargs):
current_fields = set(request.get_json().keys())
missing_fields = required_fields - current_fields
if missing_fields:
return jsonify({'error': 'missing fields', 'fields': list(missing_fields)}), 400 # Bad Request
resp = func(*args, **kwargs)
return resp
return wrapper
#app.route('/is_registered', methods=['POST'])
#requires_fields(['mobile'])
def is_registered():
_json = request.get_json()
keys = _json.keys()
customer = Customer()
registered = False
response = verify_required_params(['mobile'], keys)
if response:
return response
_mobile = _json['mobile']
validated = validate_mobile(_mobile)
cust, response_code = customer.get(_mobile)
if cust is not None and cust['id']:
registered = True
if not validated:
response = responses.get(MOBILE_NUMBER_NOT_VALID)
return jsonify(response)
if not registered:
response = responses.get(MOBILE_NUMBER_NOT_REGISTERED)
return jsonify(response)
response = responses.get(MOBILE_NUMBER_REGISTERED)
return jsonify(response)
It is giving me this error:
assert view_func is not None, "expected view func if endpoint is not provided."
AssertionError: expected view func if endpoint is not provided.
What is causing this issue? How can we fix this?
You forgot to return the decorated function from the wrapper function.
When you do #wrapper on top of a function test for instance, you are basically writing test = wrapper(test).
Since wrapper does not return anything, you get an error.
So basically you need to do:
def requires_fields(fields):
required_fields = set(fields)
def wrapper(func):
#functools.wraps(func)
def decorated(*args, **kwargs):
# ...
return decorated # you are missing this
return wrapper

Django - Logging - How to get the class name when running function GET inside it?

So I tried to create a logging for my code:
views.py
class name_of_API(generics.GenericAPIView):
#my_logging
#swagger_auto_schema
def get(self, request):
**running something
return ...
my_logging.py
def func_detail(func):
#wraps(func)
def func_wrapper(request, *args, **kwargs):
ex = None
try:
response = func(request, *args, **kwargs)
log = logging.getLogger('main')
log.info(str(func.__class__.__name__)+ ' Called success')
print(func.__class__.__name__)
return response
except:
log = logging.getLogger('internal')
ex = Exception
ex_type, ex_value, ex_traceback = sys.exc_info()
log.debug(ex_type(ex_value).with_traceback(ex_traceback))
raise ex_type(ex_value).with_traceback(ex_traceback)
return func_wrapper
format in settings.py
'format': '%(asctime)s.%(msecs)03d|%(levelname)s|%(process)d:%(thread)d|%(filename)s:%(lineno)d|%(module)s.%(funcName)s|%(message)s'
info.log:
2020-12-21 10:30:04.845|INFO|12560:11868|logger.py:11|logger.func_wrapper| function Called success
The problem here:
This row below
func.__class__.__name__
return function instead of name_of_API

How to use a dict returned by a decorator as a variable?

I am working on developing a flask app using flask-restx. In there, I would like to decorate the routes with a decorator named cognito_check_groups that checks the token, verifies it, and returns the claims.
I would like to know is there a way I can save the returning claims as a variable so that I can use its content further
My code:
def cognito_check_groups(groups: list):
def decorator(function):
def wrapper(*args, **kwargs):
_cognito_check_groups(groups)
return function(*args, **kwargs)
return wrapper
return decorator
def _cognito_check_groups(groups: list):
token = None
if 'x-access-token' in request.headers:
token = request.headers['x-access-token']
if not token:
return abort(
401,
'Missing token'
)
headers = jwt.get_unverified_headers(token)
kid = headers['kid']
key_index = -1
for i in range(len(keys)):
if kid == keys[i]['kid']:
key_index = i
break
if key_index == -1:
print('Public key not found in jwks.json')
return False
public_key = jwk.construct(keys[key_index])
message, encoded_signature = str(token).rsplit('.', 1)
decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))
if not public_key.verify(message.encode("utf8"), decoded_signature):
print('Signature verification failed')
return False
print('Signature successfully verified')
claims = jwt.get_unverified_claims(token)
return claims
I have used the decorator as follows:
#crud_ns.route("/insert")
class InsertVendor(Resource):
#crud_ns.doc(
"Insert data into the DB",
responses={
200: "Values returned",
400: "Validation Error",
401: "Not authorized",
409: "Conflct"
},
)
#crud_ns.doc(security='apikey')
#crud_ns.expect(crud_insert_parser, validation=True)
#cognito_check_groups(['admin'])
def post(self):
args = crud_insert_parser.parse_args()
return Insert.insert(**args)
Although i dont know much about Flask, usually you could something like this:
def cognito_check_groups(groups: list):
def decorator(function):
def wrapper(*args, **kwargs):
claims = _cognito_check_groups(groups)
# add claims to kwargs!
kwargs.update({'claims': claims})
return function(*args, **kwargs)
return wrapper
return decorator
...
#cognito_check_groups(['admin'])
def post(self, *args, **kwargs):
claims = kwargs.get('claims', {})
args = crud_insert_parser.parse_args()
return Insert.insert(**args)

How do I use the functions within this Python script?

I have this Python script to control a PfSense router via FauxAPI. The problem is that when i call a function it gives an error. I think i'm calling the function wrong. Does anyone know how to call them?
Here is a link to the API i'm using: https://github.com/ndejong/pfsense_fauxapi
I have tried calling config_get(self, section=none) but that does not seem to work.
import os
import json
import base64
import urllib
import requests
import datetime
import hashlib
class PfsenseFauxapiException(Exception):
pass
class PfsenseFauxapi:
host = '172.16.1.1'
proto = None
debug = None
version = None
apikey = 'key'
apisecret = 'secret'
use_verified_https = None
def __init__(self, host, apikey, apisecret, use_verified_https=False, debug=False):
self.proto = 'https'
self.base_url = 'fauxapi/v1'
self.version = __version__
self.host = host
self.apikey = apikey
self.apisecret = apisecret
self.use_verified_https = use_verified_https
self.debug = debug
if self.use_verified_https is False:
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
def config_get(self, section=None):
config = self._api_request('GET', 'config_get')
if section is None:
return config['data']['config']
elif section in config['data']['config']:
return config['data']['config'][section]
raise PfsenseFauxapiException('Unable to complete config_get request, section is unknown', section)
def config_set(self, config, section=None):
if section is None:
config_new = config
else:
config_new = self.config_get(section=None)
config_new[section] = config
return self._api_request('POST', 'config_set', data=config_new)
def config_patch(self, config):
return self._api_request('POST', 'config_patch', data=config)
def config_reload(self):
return self._api_request('GET', 'config_reload')
def config_backup(self):
return self._api_request('GET', 'config_backup')
def config_backup_list(self):
return self._api_request('GET', 'config_backup_list')
def config_restore(self, config_file):
return self._api_request('GET', 'config_restore', params={'config_file': config_file})
def send_event(self, command):
return self._api_request('POST', 'send_event', data=[command])
def system_reboot(self):
return self._api_request('GET', 'system_reboot')
def system_stats(self):
return self._api_request('GET', 'system_stats')
def interface_stats(self, interface):
return self._api_request('GET', 'interface_stats', params={'interface': interface})
def gateway_status(self):
return self._api_request('GET', 'gateway_status')
def rule_get(self, rule_number=None):
return self._api_request('GET', 'rule_get', params={'rule_number': rule_number})
def alias_update_urltables(self, table=None):
if table is not None:
return self._api_request('GET', 'alias_update_urltables', params={'table': table})
return self._api_request('GET', 'alias_update_urltables')
def function_call(self, data):
return self._api_request('POST', 'function_call', data=data)
def system_info(self):
return self._api_request('GET', 'system_info')
def _api_request(self, method, action, params=None, data=None):
if params is None:
params = {}
if self.debug:
params['__debug'] = 'true'
url = '{proto}://{host}/{base_url}/?action={action}&{params}'.format(
proto=self.proto, host=self.host, base_url=self.base_url, action=action, params=urllib.parse.urlencode(params))
if method.upper() == 'GET':
res = requests.get(
url,
headers={'fauxapi-auth': self._generate_auth()},
verify=self.use_verified_https
)
elif method.upper() == 'POST':
res = requests.post(
url,
headers={'fauxapi-auth': self._generate_auth()},
verify=self.use_verified_https,
data=json.dumps(data)
)
else:
raise PfsenseFauxapiException('Request method not supported!', method)
if res.status_code == 404:
raise PfsenseFauxapiException('Unable to find FauxAPI on target host, is it installed?')
elif res.status_code != 200:
raise PfsenseFauxapiException('Unable to complete {}() request'.format(action), json.loads(res.text))
return self._json_parse(res.text)
def _generate_auth(self):
# auth = apikey:timestamp:nonce:HASH(apisecret:timestamp:nonce)
nonce = base64.b64encode(os.urandom(40)).decode('utf-8').replace('=', '').replace('/', '').replace('+', '')[0:8]
timestamp = datetime.datetime.utcnow().strftime('%Y%m%dZ%H%M%S')
hash = hashlib.sha256('{}{}{}'.format(self.apisecret, timestamp, nonce).encode('utf-8')).hexdigest()
return '{}:{}:{}:{}'.format(self.apikey, timestamp, nonce, hash)
def _json_parse(self, data):
try:
return json.loads(data)
except json.JSONDecodeError:
pass
raise PfsenseFauxapiException('Unable to parse response data!', data)
Without having tested the above script myself, I can conclude that yes you are calling the function wrong. The above script is rather a class that must be instantiated before any function inside can be used.
For example you could first create an object with:
pfsense = PfsenseFauxapi(host='<host>', apikey='<API key>', apisecret='<API secret>')
replacing <host>, <API key> and <API secret> with the required values
Then call the function with:
pfsense.config_get() # self is not passed
where config_get can be replaced with any function
Also note
As soon as you call pfsense = PfsenseFauxapi(...), all the code in
the __init__ function is also run as it is the constructor (which
is used to initialize all the attributes of the class).
When a function has a parameter which is parameter=something, that something is the default value when nothing is passed for that parameter. Hence why use_verified_https, debug and section do not need to be passed (unless you want to change them of course)
Here is some more information on classes if you need.
You need to create an object of the class in order to call the functions of the class. For example
x = PfsenseFauxapi() (the init method is called during contructing the object)
and then go by x.'any function'. Maybe name the variable not x for a good naming quality.

Categories

Resources