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.
Related
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
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 am trying to create an api endpoint in Web2py and need to know what URLs where generated.
I tryed http://127.0.0.1:8000/contacts/api/v1/employees.json and got an api json page but when I try http://127.0.0.1:8000/contacts/api/v1/employees/1.json I get:
no matching pattern
api.py under controllers:
def v1():
response.view = 'generic.' + request.extension
def GET(*args, **vars):
patterns = 'auto'
parser = db.parse_as_rest(patterns, args, vars)
if parser.status == 200:
return dict(content=parser.response)
else:
raise HTTP(parser.status, parser.error)
def POST(table_name, **vars):
return db[table_name].validate_and_insert(**vars)
def PUT(table_name, record_id, **vars):
return db(db[table_name]._id == record_id).update(**vars)
def DELELTE(table_name, record_id):
return db(db[table_name]._id == record_id).delete()
return dict(GET=GET, POST=POST, PUT=PUT, DELELTE=DELELTE)```
I'm new to Python and I have factory class that takes in API arguments and with my setup i get a NameError: 'self' is not defined. I understand that my functions calls are wrong. How would I refactor this to have the same logic
import requests
import json
class Call:
results = []
__response = None
__methods= dict(post=self.__post(), get=self.__get())
def __init__(self, root, endpoint, payload, header):
self.__root = root
self.__endpoint = endpoint
self.__payload = payload
self.__header = header
def __post(self):
self.__response = requests.post(
self.__root + self.__endpoint,
data = json.dumps(self.__payload),
headers = self.__header
)
self.__get_results()
def __get(self):
self.__response = requests.get(
self.__root + self.__endpoint,
data = json.dumps(self.__payload),
headers = self.__header
)
self.__get_results()
def __get_results(self):
if (self.__response.ok):
data = json.loads(self.__response.content)
results.append(
{
'result':data['result'],
'endpoint': self.__endpoint,
'status' : response.status_code
}
)
else:
results.append(
{
'result':'FAILED',
'endpoint': self.__endpoint,
'status' : response.status_code
}
)
def method(self, method):
return self.__methods[method]
login = Call(
Url.V1_PROD,
DriverEndpoint.LOGIN,
DriverPayload.LOGIN,
Request.HEADER
)
login.method('post')
You shouldn't use variable names starting with a __double __underscore, they are used to invoke name mangling, which you probably don't want.
Use a _single _underscore.
It's easier to declare your dictionary on the instance, in the __init__ method.
A common alternative would be to store the names of the methods you want to call, as strings, and use getattr to access the methods (see Call a Python method by name).
import requests
import json
class Call:
results = []
_response = None
def __init__(self, root, endpoint, payload, header):
self._root = root
self._endpoint = endpoint
self._payload = payload
self._header = header
# NO () after self._post, otherwise it would call the
# method and insert the return value in the dict
self._methods= dict(post=self._post, get=self._get)
def _post(self):
self._response = requests.post(
self._root + self._endpoint,
data = json.dumps(self._payload),
headers = self._header
)
self._get_results()
def _get(self):
self._response = requests.get(
self._root + self._endpoint,
data = json.dumps(self._payload),
headers = self._header
)
self._get_results()
def _get_results(self):
if (self.__response.ok):
data = json.loads(self.__response.content)
results.append(
{
'result':data['result'],
'endpoint': self._endpoint,
'status' : response.status_code
}
)
else:
results.append(
{
'result':'FAILED',
'endpoint': self._endpoint,
'status' : response.status_code
}
)
def method(self, method):
# Here, we have to effectively call the selected method,
# hence the () at the end
self._methods[method]()
login = Call(
Url.V1_PROD,
DriverEndpoint.LOGIN,
DriverPayload.LOGIN,
Request.HEADER
)
login.method('post')
__methods= dict(post=self.__post(), get=self.__get())
Your __methods variable is class variable, self is used to refer to an instance of Call, self cannot access to the class variable scope.
You can declare __method as an instance variable:
def __init__(self):
self.__methods= dict(post=self.__post(), get=self.__get())
def get_mothods(self):
return self.__methods
How do I use Flask-Cache #cache.cached() decorator with Flask-Restful? For example, I have a class Foo inherited from Resource, and Foo has get, post, put, and delete methods.
How can I can invalidate cached results after a POST?
#api.resource('/whatever')
class Foo(Resource):
#cache.cached(timeout=10)
def get(self):
return expensive_db_operation()
def post(self):
update_db_here()
## How do I invalidate the value cached in get()?
return something_useful()
As Flask-Cache implementation doesn't give you access to the underlying cache object, you'll have to explicitly instantiate a Redis client and use it's keys method (list all cache keys).
The cache_key method is used to override the default key generation in your cache.cached decorator.
The clear_cache method will clear only the portion of the cache corresponding to the current resource.
This is a solution that was tested only for Redis and the implementation will probably differ a little when using a different cache engine.
from app import cache # The Flask-Cache object
from config import CACHE_REDIS_HOST, CACHE_REDIS_PORT # The Flask-Cache config
from redis import Redis
from flask import request
import urllib
redis_client = Redis(CACHE_REDIS_HOST, CACHE_REDIS_PORT)
def cache_key():
args = request.args
key = request.path + '?' + urllib.urlencode([
(k, v) for k in sorted(args) for v in sorted(args.getlist(k))
])
return key
#api.resource('/whatever')
class Foo(Resource):
#cache.cached(timeout=10, key_prefix=cache_key)
def get(self):
return expensive_db_operation()
def post(self):
update_db_here()
self.clear_cache()
return something_useful()
def clear_cache(self):
# Note: we have to use the Redis client to delete key by prefix,
# so we can't use the 'cache' Flask extension for this one.
key_prefix = request.path
keys = [key for key in redis_client.keys() if key.startswith(key_prefix)]
nkeys = len(keys)
for key in keys:
redis_client.delete(key)
if nkeys > 0:
log.info("Cleared %s cache keys" % nkeys)
log.info(keys)
Yes, you can use like that.
Maybe you will still need to read: flask-cache memoize URL query string parameters as well
You can invalidate cache using cache.clear() method.
For more detials see: https://pythonhosted.org/Flask-Cache/#flask.ext.cache.Cache.clear and Clearing Cache section in https://pythonhosted.org/Flask-Cache/
##create a decarator
from werkzeug.contrib.cache import SimpleCache
CACHE_TIMEOUT = 300
cache = SimpleCache()
class cached(object):
def __init__(self, timeout=None):
self.timeout = timeout or CACHE_TIMEOUT
def __call__(self, f):
def decorator(*args, **kwargs):
response = cache.get(request.path)
if response is None:
response = f(*args, **kwargs)
cache.set(request.path, response, self.timeout)
return response
return decorator
#add this decarator to your views like below
#app.route('/buildingTotal',endpoint='buildingTotal')
#cached()
def eventAlert():
return 'something'
#app.route('/buildingTenants',endpoint='buildingTenants')
#cached()
def buildingTenants():
return 'something'
Answer from #JahMyst didn't work for me.
Flask-Cache doesn’t work with Flask restful framework. #cache.Cached & #cache.memoize can’t handle mutable objects per their documentation.
Using mutable objects (classes, etc) as part of the cache key can become tricky. It is suggested to not pass in an object instance into a memoized function. However, the memoize does perform a repr() on the passed in arguments so that if the object has a __repr__ function that returns a uniquely identifying string for that object, that will be used as part of the cache key.
Had to come-up with my own implementation. Leaving this code snippet incase someone else gets stuck with the same issue.
cache_key function converts the user req into hash.
cache_res_pickled function is being used to pickle or unpickle the data
|-flask-app
|-app.py
|-resource
|--some_resource.py
import json
import logging
import pickle
import time
import urllib
from flask import Response, abort, request
from redis import Redis
redis_client = Redis("127.0.0.1", "6379")
exp_setting_s = 1500
def json_serial(obj):
"""
JSON serializer for objects not serializable by default json code"
Args:
obj: JSON serialized object for dates
Returns:
serialized JSON data
"""
if isinstance(obj, datetime.datetime):
return obj.__str__()
def cache_key():
""" ""
Returns: Hashed string of request made by the user.
"""
args = request.args
key = (
request.path
+ "?"
+ urllib.parse.urlencode(
[(k, v) for k in sorted(args) for v in sorted(args.getlist(k))]
)
)
key_hashed = hashlib.sha256(key.encode())
return key_hashed.hexdigest()
def cache_res_pickled(data, encode):
"""
Args:
data (dict): Data in dict format
encode (Boolean): Encode (true) or decode (false) the data
Returns: Result after pickling
"""
if encode:
return pickle.dumps(data)
else:
data = pickle.loads(data)
return data
class SomeResource(Resource):
#auth.login_required
def get(self):
# Get the key for request in hashed format SHA256
key = cache_key()
result = redis_client.get(key)
def generate():
"""
A lagging generator to stream JSON so we don't have to hold everything in memory
This is a little tricky, as we need to omit the last comma to make valid JSON,
thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
"""
releases = res.__iter__()
try:
prev_release = next(releases) # get first result
# We have some releases. First, yield the opening json
yield '{"data": ['
# Iterate over the releases
for release in releases:
yield json.dumps(prev_release, default=json_serial) + ", "
prev_release = release
logging.info(f"For {key} # records returned = {len(res)}")
# Now yield the last iteration without comma but with the closing brackets
yield json.dumps(prev_release, default=json_serial) + "]}"
except StopIteration:
# StopIteration here means the length was zero, so yield a valid releases doc and stop
logging.info(f"For {key} # records returned = {len(res)}")
yield '{"data": []}'
if result is None:
# Secure a key on Redis server.
redis_client.set(key, cache_res_pickled({}, True), ex=exp_setting_s)
try:
# Do the querying to the DB or math here to get res. It should be in dict format as shown below
res = {"A": 1, "B": 2, "C": 2}
# Update the key on Redis server with the latest data
redis_client.set(key, cache_res_pickled(res, True), ex=exp_setting_s)
return Response(generate(), content_type="application/json")
except Exception as e:
logging.exception(e)
abort(505, description="Resource not found. error - {}".format(e))
else:
res = cache_res_pickled(result, False)
if res:
logging.info(
f"The data already exists!😊 loading the data form Redis cache for Key - {key} "
)
return Response(generate(), content_type="application/json")
else:
logging.info(
f"There is already a request for this key. But there is no data in it. Key: {key}."
)
s = time.time()
counter = 0
# loops aimlessly till the data is available on the Redis
while not any(res):
result = redis_client.get(key)
res = cache_res_pickled(result, False)
counter += 1
logging.info(
f"The data was available after {time.time() - s} seconds. Had to loop {counter} times.🤦"
)
return Response(generate(), content_type="application/json")
Inspired from durga's answer I wrote a very basic decorator which uses redis directly instead of any library.
from src.consts import config
from src.utils.external_services import redis_connector
import json
import jsons
import base64
class cached(object):
def __init__(self, req, timeout=None):
self.timeout = timeout or config.CACHE_DEFAULT_TIMEOUT
self.request = req
self.cache = redis_connector.get_redis_instance()
def __call__(self, f):
def decorator(*args, **kwargs):
redis_healthy = True
if self.cache is not None:
try:
self.cache.ping()
except Exception as ex:
redis_healthy = False
else:
redis_healthy = False
if self.request is not None and self.request.values is not None and self.request.path is not None and redis_healthy:
cache_key = "{}-{}".format(self.request.path, json.dumps(jsons.dump(self.request.values), sort_keys=True))
cache_key_base_64 = base64.b64encode(cache_key.encode("ascii")).decode("ascii")
response = self.cache.get(cache_key_base_64)
if response is None:
response = f(*args, **kwargs)
self.cache.setex(cache_key_base_64, self.timeout, jsons.dumps(response))
else:
response = json.loads(response)
else:
response = f(*args, **kwargs)
return response
return decorator
Now use this decorator on your api functions
from flask import g, request
from flask_restful import Resource
from webargs.flaskparser import use_args
class GetProducts(Resource):
#use_args(gen_args.argsGetProducts)
#cached(request)
def get(self, args):
return "hello from products"