Object Oriented Python with Flask Server? - python

I'm using Flask to expose some data-crunching code as a web service.
I'd like to have some class variables that my Flask functions can access.
Let me walk you through where I'm stuck:
from flask import Flask
app = Flask(__name__)
class MyServer:
def __init__(self):
globalData = json.load(filename)
#app.route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
if __name__ == "__main__":
app.run(host='0.0.0.0')
When I run getSomeData() outside of Flask, it works fine. But, when I run this with Flask, I get 500 internal server error. There's no magic here, and Flask has no idea that it's supposed to initialize a MyServer object. How can I feed an instance of MyServer to the app.run() command?
I could admit defeat and put globalData into a database instead. But, is there an other way?

You can create an instance of MyServer just outside the scope of your endpoints and access its attributes. This worked for me:
class MyServer:
def __init__(self):
self.globalData = "hello"
from flask import Flask
app = Flask(__name__)
my_server = MyServer()
#app.route("/getSomeData")
def getSomeData():
return my_server.globalData
if __name__ == "__main__":
app.run(host="0.0.0.0")

I know this is a late reply, but I came across this question while facing a similar issue. I found flask-classful really good.
You inherit your class from FlaskView and register the Flask app with your MyServer class
http://flask-classful.teracy.org/#
In this case, with flask-classful, your code would look like this:
from flask import Flask
from flask_classful import FlaskView, route
app = Flask(__name__)
class MyServer(FlaskView):
def __init__(self):
globalData = json.load(filename)
#route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
MyServer.register(app, base_route="/")
if __name__ == "__main__":
app.run(host='0.0.0.0')

The least-coupled solution is to apply the routes at runtime (instead of at load time):
def init_app(flask_app, database_interface, filesystem_interface):
server = MyServer(database_interface, filesystem_interface)
flask_app.route('get_data', methods=['GET'])(server.get_data)
This is very testable--just invoke init_app() in your test code with the mocked/faked dependencies (database_interface and filesystem_interface) and a flask app that has been configured for testing (app.config["TESTING"]=True or something like that) and you're all-set to write tests that cover your entire application (including the flask routing).
The only downside is this isn't very "Flasky" (or so I've been told); the Flask idiom is to use #app.route(), which is applied at load time and is necessarily tightly coupled because dependencies are hard-coded into the implementation instead of injected into some constructor or factory method (and thus complicated to test).

The following code is a simple solution for OOP with Flask:
from flask import Flask, request
class Server:
def __init__(self, name):
self.app = Flask(name)
#self.app.route('/')
def __index():
return self.index()
#self.app.route('/hello')
def __hello():
return self.hello()
#self.app.route('/user_agent')
def __user_agent():
return self.user_agent()
#self.app.route('/factorial/<n>', methods=['GET'])
def __factorial(n):
return self.factorial(n)
def index(self):
return 'Index Page'
def hello(self):
return 'Hello, World'
def user_agent(self):
return request.headers.get('User-Agent')
def factorial(self, n):
n = int(n)
fact = 1
for num in range(2, n + 1):
fact = fact * num
return str(fact)
def run(self, host, port):
self.app.run(host=host, port=port)
def main():
server = Server(__name__)
server.run(host='0.0.0.0', port=5000)
if __name__ == '__main__':
main()
To test the code, browse the following urls:
http://localhost:5000/
http://localhost:5000/hello
http://localhost:5000/user_agent
http://localhost:5000/factorial/10

a bit late but heres a quick implementation that i use to register routes at init time
from flask import Flask,request,render_template
from functools import partial
registered_routes = {}
def register_route(route=None):
#simple decorator for class based views
def inner(fn):
registered_routes[route] = fn
return fn
return inner
class MyServer(Flask):
def __init__(self,*args,**kwargs):
if not args:
kwargs.setdefault('import_name',__name__)
Flask.__init__(self,*args ,**kwargs)
# register the routes from the decorator
for route,fn in registered_routes.items():
partial_fn = partial(fn,self)
partial_fn.__name__ = fn.__name__
self.route(route)(partial_fn)
#register_route("/")
def index(self):
return render_template("my_template.html")
if __name__ == "__main__":
MyServer(template_folder=os.path.dirname(__file__)).run(debug=True)

if you wish to approach MyServer class as a resource
I believe that flask_restful can help you:
from flask import Flask
from flask_restful import Resource, Api
import json
import numpy as np
app = Flask(__name__)
api = Api(app)
class MyServer(Resource):
def __init__(self):
self.globalData = json.load(filename)
def get(self):
return np.random.choice(self.globalData)
api.add_resource(MyServer, '/')
if __name__ == '__main__':
app.run()

Related

cant run function, because signal only works in main thread of the main interpreter?

I'm trying to build an API using Flask. If the endpoint /register/string:token is retrieved and the token is in a defined list, the function register_user(username) should be executed. This function sends a command to another server using the rcon protocol. However, as soon as the function is called, I get the following error: ValueError: signal only works in main thread of the main interpreter. How can I fix the problem? Or, how can I run the function in the main thread?
My code looks something like this (Note: The script runs fine on Windows):
from flask import Flask, send_file
from mcrcon import MCRcon as rcon
import configparser
api = Flask(__name__)
# #api.route('/register/<string:token>', methods=['GET'])
def register(token):
cache = utils.load_cache()
if token in list(cache['register_tokens'].keys()):
utils.register_user(username=cache['register_tokens'][token]) # problem!
del cache['register_tokens'][token]
utils.update_cache(obj=cache)
return {'status':'registration successful'}
else:
return {'status':'registration failed'}
class utils:
#staticmethod
def load_cache(file='../cache.pkl'):
...
#staticmethod
def update_cache(obj, file='../cache.pkl'):
...
#staticmethod
def clear_cache():
...
#staticmethod
def register_user(username):
with rcon(config['global']['server_ip'], config['global']['server_rcon_secret']) as console:
r = console.command(...)
console.disconnect()
if __name__ == '__main__':
config = configparser.ConfigParser()
config.read('../config.conf')
if bool(config['discordbot.py']['enable'])==True:
api.add_url_rule('/register/<string:token>', 'register', register)
api.run(host=config['global']['server_ip'], debug=False, use_reloader=False)

Outside context error when working from blueprint flask python

I have this simple webapp written in python (Flask)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Coin(db.Model):
__tablename__ = "coins"
id = db.Column(db.Integer, primary_key=True)
pair = db.Column(db.String)
sell_amt = db.Column(db.Float)
buy_amt = db.Column(db.Float)
app.py
from flask import Flask
from ui import ui
from models import db , Coin
app = Flask(__name__)
app.register_blueprint(ui)
db.init_app(app)
if __name__ == "__main__":
app.run(port=8080)
__init__.py in ui folder
from flask import Blueprint ,current_app
from models import db, Coin
from threading import Thread
ui = Blueprint('ui', __name__)
def intro():
global bot_state
with current_app.app_context():
all_coins = Coin.query.filter_by().all()
while bot_state:
sleep(3)
print (f" Current time : {time()}")
#ui.route('/startbot')
def start_bot():
global bot_thread, bot_state
bot_state = True
bot_thread = Thread(target=intro ,daemon=True)
bot_thread.start()
return "bot started "
#ui.route('/stopbot')
def stop_bot():
global bot_state
bot_state = False
bot_thread.join()
return " bot stopped"
When create a request to /startbot the app throws the error the it is working outside the app context
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
but when trying to create a database object for example new = Coin() it works fine, how do you give a function the context of the application without making a function that returns the app, because doing so creates another error that is (circular import)
Note this is the bare minimum example and there are other files that require access to the models.py folder (to add orders to the data base created by the bot )
There has to be a better way of doing it but this is what I managed to do, we create two apps the first one is the main web app, and looks sth like this
app = Flask(__name__)
app.register_blueprint(some_blueprint)
db.init_app(app)
and the second app will be for the bot and will be declared in the same file where the bot core code is written and can be imported into the blueprint and looks like this
bot_app = Flask(__name__)
db.init_app(app)
Now intro will look sth like this
from bot_file import bot_app
def intro(app):
with bot_app.app_context():
all_coins = Coin.query.all()
this way we can use the bot_app in the bot_core class with out importing the main web app
This isn't the most preferable code out there but it does solve this problem
The trick is to pass the application object to the thread. This also works with the proxy current_app. In this case, however, you need access to the underlying application object. You can find a short note on this within the documentation here.
from flask import current_app
# ...
def intro(app):
with app.app_context():
all_coins = Coin.query.all()
#ui.route('/startbot')
def start_bot():
bot_thread = Thread(
target=intro,
args=(current_app._get_current_object(),), # <- !!!
daemon=True
)
bot_thread.start()
return "bot started"
Since you don't seem to have fully understood my explanations, the following is how the complete contents of the __init__.py file would look like.
from flask import Blueprint, current_app, render_template
from models import Coin, db
from threading import Event, Lock, Thread
from time import sleep, time
ui = Blueprint('ui', __name__)
thread = None
thread_event = Event()
thread_lock = Lock()
def intro(app, event):
app.logger.info('bot started')
try:
while event.is_set():
tm = time()
app.logger.info('current time %s', tm)
with app.app_context():
all_coins = Coin.query.all()
# ...
dur = 3 - (time() - tm)
if dur > 0: sleep(dur)
finally:
event.clear()
app.logger.info('bot stopped')
#ui.route('/startbot')
def start_bot():
global thread
thread_event.set()
with thread_lock:
if thread is None:
thread = Thread(
target=intro,
args=(current_app._get_current_object(), thread_event),
daemon=True
)
thread.start()
return '', 200
#ui.route('/stopbot')
def stop_bot():
global thread
thread_event.clear()
with thread_lock:
if thread is not None:
thread.join()
thread = None
return '', 200
Have fun and success with the further implementation of your project.

Using Pytest to monkeypatch an initialized redis connection in a flask application

I've been struggling with this for awhile now. I Have a flask app that is executed in my app.py file. In this file I have a bunch of endpoints that call different functions from other files. In another file, extensions.py, I've instantiated a class that contains a redis connection. See the file structure below.
#app.py
from flask import Flask
from extensions import redis_obj
app = Flask(__name__)
#app.route('/flush-cache', methods=['POST'])
def flush_redis():
result = redis_obj.flush_redis_cache()
return result
# extensions.py
from redis_class import CloudRedis
redis_obj = CloudRedis()
# redis_class
import redis
class CloudRedis:
def __init__(self):
self.conn = redis.Redis(connection_pool=redis.ConnectionPool.from_url('REDIS_URL',
ssl_cert_reqs=None))
def flush_redis_cache(self):
try:
self.conn.flushdb()
return 'OK'
except:
return 'redis flush failed'
I've been attempting to use monkeypatching in a test patch flush_redis_cache, so when I run flush_redis() the call to redis_obj.flush_redis_cache() will just return "Ok", since I've already tested the CloudRedis class in other pytests. However, no matter what I've tried I haven't been able to successfully patch this. This is what I have below.
from extensions import redis_obj
from app import app
#pytest.fixture()
def client():
yield app.test_client()
def test_flush_redis_when_redis_flushed(client, monkeypatch):
# setup
def get_mock_flush_redis_cache():
return 'OK'
monkeypatch.setattr(cloud_reids, 'flush_redis_cache', get_mock_flush_redis_cache)
cloud_redis.flush_redis = get_mock_flush_redis_cache
# act
res = client.post('/flush-cache')
result = flush_redis()
Does anyone have any ideas on how this can be done?

Pass variable to Python web.py webserver for use by GET/POST handling

I have a simple web.py webserver. Upon a POST request to the webserver, I want to modify an object that I created inside main(). But I can't figure out how to inject a variable into the webserver so that the index class has access to it.
Here is my code:
import web
import multiprocessing
class index:
def POST(self):
# modify 'obj' instance of MyObject from main()
class MyWebApp(web.application):
def run(self, port=8080, *middleware):
func = self.wsgifunc(*middleware)
return web.httpserver.runsimple(func, ('0.0.0.0', port))
urls = ( '/', 'index' )
app = MyWebApp(urls, globals())
def launchWebApp(_port=8080):
app.run(port=_port)
def main():
obj = MyObject()
port=36011
p = multiprocessing.Process(target=launchWebApp, args=(port,))
p.start()
if __name__ == "__main__":
main()
How do I make obj accessible to index::POST?
You can't really "pass" it, but you can make your obj global, or you can attach it to web.config, which is (also) global. For example.
class index:
def POST(self):
web.config.obj.update({'foo': 'bar'})
...
def main():
web.config.obj = MyObject()
...

Python Mock Testing to mock session

I am learning how to test functions in Python using Mock. I am trying to write a test for a simple function,
#social.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return redirect(url_for('social.index'))
else:
return render_template("/home.html")
However, I am stuck at how to try and mock session.get('uid') value. So far, this has been my attempt,
#patch('session', return_value='')
#patch('flask.templating._render', return_value='')
def test_mocked_render(self, mocked, mock_session):
print "mocked", repr(self.app.get('/social/friends').data)
print "was _render called?", mocked.called
This attempt may be completely wrong and this is definitely the wrong way as I am still not able to mock session. However, can someone please guide me in the right way through this? Thanks.
Starting with Flask 0.8 we provide a so called “session transaction” which simulates the appropriate calls to open a session in the context of the test client and to modify it.
Let's give a simple example: app.py
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'very secret'
#app.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return 'uid not in session'
else:
return 'uid in session'
if __name__ == '__main__':
app.run(debug=True)
The test file: test_app.py
import unittest
from app import app
class TestSession(unittest.TestCase):
def test_mocked_session(self):
with app.test_client() as c:
with c.session_transaction() as sess:
sess['uid'] = 'Bar'
# once this is reached the session was stored
rv = c.get('/friends')
assert rv.data == 'uid in session'
if __name__ == '__main__':
unittest.main()
run the tests via python test_app.py.
Documentation: Accessing and Modifying Sessions

Categories

Resources