Acessing "app" (Eve object) from hook callbacks - python

I'm using hooks in my Eve app to update a "summary" object every time a new item is added to my collection. To keep things clean, I've moved my callbacks to a separate dir/file that I import from run.py where I set up the hooks.
My problem is that I need to access the Eve() object (that I called "app") from inside my callback function (named on_inserted_expense). I couldn't find the "eve" way to do it, so I ended up using something like this decorator-like trick, which works:
from eve import Eve
from eventhooks import posthooks
from functools import wraps
app = Eve()
def passing_app(f):
#wraps(f)
def wrapper(*args, **kwargs):
kwargs['app'] = app
return f(*args, **kwargs)
return wrapper
app.on_inserted_expenses += passing_app(posthooks.on_inserted_expense)
That way from eventhooks/posthooks.py I can do:
def on_inserted_expense(items, **kwargs):
app = kwargs['app']
for item in items:
summaries = app.data.driver.db['summaries']
summary = summaries.find_one({'title': 'default'})
if not item_in_summary(item, summary):
with app.test_request_context():
update = update_summary(summary, item)
patch_internal(summary, payload=update, concurrency_check=True)
My question, therefore, is: is there a way to retrieve the current "app" object from Eve in a cleaner way from anywhere within the application? If not, would that be something worth adding, maybe in the way of a singleton? Thanks!

I have been doing this:
from flask import current_app
And using current_app as the app.
Reference: http://flask.pocoo.org/docs/0.10/api/#flask.current_app
Are there pitfalls I should be aware of doing this? It seems to work when adding hooks.

You probably want to to follow the Larger Flask Application pattern , so you can have your app object declared in your __init__.py and then you can import it anywhere you want. Remember, Eve is just a Flask application so whatever you can do with Flask you can generally do with Eve.

Related

Updating DataFrame while API is running in Python

So I am trying to create an API that constantly reads from a CSV and returns information about it when requested. So far, I have created a flask API that reads the CSV file once and returns correctly. However, I can't seem to make it constantly update. My working code is something like this.
app = flask.Flask(__name__)
app.config["DEBUG"] = True
dfchat = pd.read_csv(path)
escaper = None
# for now, this is just to make sure the program keeps running even if there is an error
def escape_route():
global escaper
while escaper != "Y":
escaper = str(input("Exit now? Enter \'Y\': \n")).strip()
os._exit(os.X_OK)
def sample_function(dfchat):
#app.route('/sample_text', methods=['GET'])
def sample_endpoint():
# this function filters dfchat and returns whatever
def main():
global dfchat
escape_route_thread = threading.Thread(target = escape_route)
escape_route_thread.start()
sample_function(dfchat)
app.run()
main()
I have tried creating another thread that updates the CSV file:
def retrieve_database():
global dfchat
while True:
time.sleep(0.1)
dfchat = pd.read_csv(path)
along with:
escape_route_thread = threading.Thread(target = retrieve_database)
escape_route_thread.start()
in the main function.
But that fails to update the dfchat data frame when the API launches. I have tested the thread by itself and it does update and return an updated data frame.
From what I understand so far, once an API runs, python code cannot change the API itself.
So,
Is there a way to update a running API with just python?
I'm asking for just python because I will not be able to manually enter a link like "/refresh" to do this. It has to be done by python.
Am I missing something?
Thank you very much for helping!
Edit:
I also tried to update the csv file for every API call. But that does but work either:
def sample_function():
dfchat = pd.read_csv(path)
#app.route('/sample_text', methods=['GET'])
def sample_endpoint():
# this function filters dfchat and returns whatever
Code defined at the root of the script (as was dfchat definition in your example) is executed once at the moment you start the flask server.
Code inside a Flask app route (function decorated with #app.route(...)) is executed at each API call to this route.
from flask import Flask
app = Flask(__name__)
path = "path/to/your/csv/file.csv"
#app.route('/sample_text', methods=['GET'])
def sample_endpoint():
dfchat = pd.read_csv(path)
# do what you have to do with the DF
Also note that Flask handles errors without stopping the API, and has great documentation to help you : https://flask.palletsprojects.com/en/2.0.x/quickstart/#a-minimal-application
So I realized that the solution is really simple. I'm new to CS and APIs in general so I did not realize how #app.route worked in Flask. Outside of #app.route cannot change a route but updating a variable inside a route does work. I accidentally kept updating dfchat outside of #app.route.
def sample_function():
# instead of putting dfchat here
#app.route('/sample_text', methods=['GET'])
def sample_endpoint():
dfchat = pd.read_csv(path) # it should go here.
# this function filters dfchat and returns whatever
Thank you yco for helping me realize this.

Pytest monkeypatch not working on flask view function

Suppose we have the following Flask view function to test. Specifically, we want to mock create_foo() as it writes to the filesystem.
# proj_root/some_project/views.py
from some_project import APP
from some_project.libraries.foo import create_foo
#APP.route('/run', methods=['POST']
def run():
bar = create_foo()
return 'Running'
Now we want to write a unit test for run() with the call to create_foo() mocked to avoid creating unnecessary files.
# proj_root/tests/test_views.py
from some_project import some_project
#pytest.fixture
def client():
some_project.APP.config['TESTING'] = True
with some_project.APP.test_client() as client:
yield client
def test_run(client, monkeypatch):
monkeypatch.setattr('some_project.libraries.foo.create_foo', lambda: None)
response = client.post('/run')
assert b'Running' in response.data
It seems that this approach should work even with the named create_foo import. The tests all pass, however the original code of create_foo is clearly being executed as a new file in the filesystem is created each time the test suite is run. What am I missing? I suspect it has something to do with the named imports based on some related questions but I'm not sure.
The correct monkeypatch is:
monkeypatch.setattr('some_project.views.create_foo', lambda: None)
The reason for this is pretty well explained here.

Single instance of class from another module

I come from Java background and most of my thinking comes from there. Recently started learning Python. I have a case where I want to just create one connection to Redis and use it everywhere in the project. Here is how my structure and code looks.
module: state.domain_objects.py
class MyRedis():
global redis_instance
def __init__(self):
redis_instance = redis.Redis(host='localhost', port=6379, db=0)
print("Redus instance created", redis_instance)
#staticmethod
def get_instance():
return redis_instance
def save_to_redis(self, key, object_to_cache):
pickleObj = pickle.dumps(object_to_cache)
redis_instance.set(key, pickleObj)
def get_from_redis(self, key):
pickled_obj = redis_instance.get(key)
return pickle.loads(pickled_obj)
class ABC():
....
Now I want to use this from other modules.
module service.some_module.py
from state.domain_objects import MyRedis
from flask import Flask, request
#app.route('/chat/v1/', methods=['GET'])
def chat_service():
userid = request.args.get('id')
message_string = request.args.get('message')
message = Message(message_string, datetime.datetime.now())
r = MyRedis.get_instance()
user = r.get(userid)
if __name__ == '__main__':
global redis_instance
MyRedis()
app.run()
When I start the server, MyRedis() __init__ method gets called and the instance gets created which I have declared as global. Still when the service tries to access it when the service is called, it says NameError: name 'redis_instance' is not defined I am sure this is because I am trying to java-fy the approach but not sure how exactly to achieve it. I read about globals and my understanding of it is, it acts like single variable to the module and thus the way I have tried doing it. Please help me clear my confusion. Thanks!

Python, Circular Dependencies, and Singletons

I've dug myself into quite a hole here.
I'm working on a Python/Kivy app in PyDev.
The app runs off of many systems (about 10), so I shoved them into an engine to handle everything.
For ease of access, I grab the engine via (the worst) singletons
main.py
#main.py
from code import engine
class MyApp(App):
def build(self):
engine.GetInstance().Initialize()
if __name__ == '__main__':
MyApp().run()
engine.py
#engine.py
from code import system1
from code import system2
gEngineInstance = None
def GetInstance():
global gEngineInstance
if (gEngineInstance == None):
gEngineInstance = Engine()
return gEngineInstance
class Engine():
mSystem1 = None
mSystem2 = None
def Initialize(self):
self.mSystem1 = system1.System1()
self.mSystem2 = system2.System2()
# Omitted
Unfortunatley, this resulted in some nasty circular dependencies.
Main has to create engine, and know about it, which runs engines imports, which runs the system imports.
Problem: Systems imports then import engine, circular reference.
system1.py
#system1.py
from code import engine
class System1():
def SomeMethod(self):
engine.GetInstance().mSystem2.DoThings()
You get the picture.
I bypassed this for now with this hideous code all over the place:
system1.py
#system1.py
class System1():
def SomeMethod(self):
from code import engine
engine.GetInstance().mSystem2.DoThings()
This stops the import from happening until that line, which is fine, but it looks wrong, eveyrthing feels like i'm doing things wrong.
I'm tempted to just pass Engine as a reference to every systems constructor, but thats a bit of refactoring, and i'd like to know if there's a more decent way to fix this sort of singleton/circular reference issue for the future.
How about having a "registration" mechanism, where each system module "registers" itself with the Engine class using some module-level code:
engine.py
class Engine():
#classmethod
def register(cls, type):
...
system1.py
from engine import Engine
class System1():
...
Engine.register(System1)
That way, the Engine doesn't directly have to know what gets plugged into it.

Using flask extensions in flask blueprints

I want to create a blueprint; not an issue with the current blueprint I have. I can do this.
But, say I wanted to use a flask extension in my application (for my case I want to integrate flask-Cache)?
Everything I've done so far has errored:
cache = Cache(my_blueprint)
importing Cache and various parts of Cache in varying guises
so something like flask-cache is simple enough to wrap around my app:
from flask.ext.cache import Cache
cache = Cache(app)
but using this in a blueprint or using with a blueprint I don't quite understand how right now.
EDIT: the less obvious solution was to crib from the extension and build my own library to import into the blueprint, but it is more work and I'm not quite done yet. extensions / blueprints don't seem to be compatible from my level of understanding right now.
In order to avoid circular imports you will want to create your cache instance separate from your application instance (you may want to consider switching to the app factory module if you are building something more complex).
cache.py
from flask_cache import Cache
cache = Cache()
foo.py
from flask import Blueprint
from cache import cache
mod = Blueprint(...)
#mod.route("/")
#cache.cached(timeout=50)
def index():
return datetime.now().strfmtime("%Y-%m-%d %H:%M:%S")
app.py
from flask import Flask
from yourapp.cache import cache
from yourapp.foo import mod
app = Flask("yourapp")
# ... snip ...
cache.init_app(app)
# ... snip ...
app.register_blueprint(mod)
The only thing the application needs access to is the app-instance to create a cache.
Let's assume your code: cache = Cache(app) is in foo.py. But you wanna use the cache in bar.py which uses a Blueprint to register the routes.
foo.py:
from flask.ext.cache import Cache
cache = Cache(app)
from bar import mod
app.register_blueprint(mod)
The only thing you have to do in bar.py is importing the cache from foo.py and use it:
bar.py:
from foo import chache
mod = Blueprint(...)
#mod.route('/')
#cache.cached(timeout=50)
def index():
return str(datetime.now())
Flask Cache Docs and Examples
EDIT: The example above has a problem with circiular imports. The way to go here is separate the app from the cache:
pack/__init__.py:
app = Flask(__name__)
from pack.views.general import mod
app.register_blueprint(mod)
pack/cache.py:
from flask.ext.cache import Cache
from pack import app
cache = Cache(app)
pack/views/general.py:
from flask import Blueprint
from pack.chache import chache
mod = Blueprint(...)
#mod.route('/')
#cache.cached(timeout=50)
def index():
return str(datetime.now())

Categories

Resources