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
Related
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
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)
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.
In Django, I have a view which makes a request to an external API. The view is in my_app.
class ExternalAPIView(View):
def get(self, request, *args, **kwargs):
...
external_api_response = requests.get(settings.EXTERNAL_API_URL)
...
For its unit tests, I use the following function for the side_effect parameter of the #patch decorator.
def mocked_requests_get_for_external_api(*args, **kwargs):
class MockResponse:
def __init__(self, content, status_code):
self.content = content
self.status_code = status_code
if args[0] == settings.EXTERNAL_API_URL:
return MockResponse(json.dumps('{"value": 1}'), 200)
return MockResponse(None, 404)
... and the unit test goes like this without an issue:
#patch("my_app.views.requests.get", side_effect= mocked_requests_get_for_external_api)
def test_external_api(self, mock_get):
response = self.client.get(settings.EXTERNAL_API_VIEW_URL)
assert response.status_code == 200
data = json.loads(response.content)
assert data["value"] == 1
However, I have another view in the same project, which calls this ExternalAPIView as follows:
class MainView(View):
def get(self, request, *args, **kwargs):
...
response = requests.get(request.build_absolute_uri(settings.EXTERNAL_API_VIEW_URL))
...
I'd like to create a unit test for this MainView, which will make the call to the ExternalAPIView through settings.EXTERNAL_API_VIEW_URL, but mock external API call inside the ExternalAPIView.
Is it possible at the first place? And if so, how can I do that?
This could be achieved by mocking only the second call to requests.get and leaving the first call as is, something like:
import requests
#patch("my_app.views.requests.get", side_effect=[requests.get, mocked_requests_get_for_external_api])
def test_main_view_api(self, mock_get):
response = self.client.get(MAIN_VIEW_URL)
assert response.status_code == 200
data = json.loads(response.content)
assert data["value"] == 1
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.