1

*Edit: just realized I made a mistake in my function design where I re-instantiated the AppDAO in my Class1 function and that is what was causing the unexpected behavior. I figured it out by printing the self argument in cache_call.

I have a Flask App with the following design:

 from flask import Flask, request
 from Class1 import Class1
 from AppDAO import AppDAO
 
 app = Flask(__name__)

 def main():
   app.config['appDAO'] = AppDAO()
   app.run()

 @app.route('/app_route1',methods=['POST'])
 def app_route1():
     print("Running app route 1...")
     print(app.config['appDAO'].cache_call.cache_info())
     
     cache_param = request.json.get('cached_parameter')
     print("The cached parameter is: %s." % cache_param)

     class1 = Class1(app.config['appDAO'])

     for item in ['item1', 'item2']:
           class1.do_processing(item,cache_param)

Class1.py:

class Class1(object):
   def __init__(self, app_dao):
     self.app_dao = app_dao

   def do_processing(self, item, cache_param):
        print("Processing for item: %s..." % item)

        resp_cache = self.app_dao.cache_call(cache_param)
        print(self.app_dao.cache_call.cache_info())
        
        return resp_cache

AppDAO.py:

from functools import lru_cache
import mysql.connector 

class AppDAO(object):
  
  def __init__():
      self.conn = mysql.connector.connect('user1','password1','server1','database')
  
  @lru_cache(maxsize=4)
  def cache_call(self, cache_param):
     print("Running cache call with parameter: %s..." % cache_param)

     cursor = self.conn.cursor()
     cursor.execute("SELECT * FROM Table1 WHERE Column1 = `%s`;" % cache_param)
     rs = cursor.fetchall()

     return rs
       

If I run the app making a post, the AppDAO.cache_call functions correctly with the following output print output:

 Running app route 1...
 CacheInfo(hits=0, misses=0, maxsize=4, currsize=0)
 Processing items: item1...
 Running cache call with parameter: foo1...
 CacheInfo(hits=0, misses=1, maxsize=4, currsize=1)
 Processing items: item2...
 CacheInfo(hits=1, misses=1, maxsize=4, currsize=1)

But I make another post to the branch using the same parameter for the cache_call, I get the following print output:

 Running app route 1...
 CacheInfo(hits=1, misses=1, maxsize=4, currsize=1)
 Processing items: item1...
 Running cache call with parameter: foo1...
 CacheInfo(hits=1, misses=2, maxsize=4, currsize=2)
 Processing items: item2...
 CacheInfo(hits=2, misses=2, maxsize=4, currsize=2)
  

I run the app using the Anaconda QT Console, but I experience the following caching issue if I used an Anaconda Command Prompt as well. Can anyone speculate why the lru_cache is not working when a new post is made to the app despite the cached call still clearing being stored?

EliSquared
  • 1,409
  • 5
  • 20
  • 44

1 Answers1

0

Note that

@lru_cache(maxsize=4)
def cache_call(self, cache_param):

is wrapping a method, not a function. In your example, self, which will be used as as part of the cache key, is an instance of Class1 and is created once per route handler invocation. The result is that you aren't getting the caching you expect.

Updated: I misread the code. Assuming that

for item in [item1, item2]:

was a typo, and should be

for item in ['item1', 'item2']:

and that you're do_processing is intentionally not passing item (which varies) to cache_call, then what you're seeing is consistent with how lru_cache behaves.

On first request, it's going to add one thing (request.json.get('cached_parameter')) to the cache, scoring it as a miss the for 'item1' and a hit for 'item2'.

On second request, request.json.get('cached_parameter') is a different object. It gets scored as a miss for 'item1', added (increasing 'currsize' to 2). For 'item2', it gets scored as a hit.

What behavior did you expect?

Unrelated but worth mentioning: The way you're constructing that query leaves you open to SQL Injection attacks. Consider using a bind parameter instead.

Dave W. Smith
  • 24,318
  • 4
  • 40
  • 46
  • I am using the same `AppDAO` object that is instantiated in the Flask app `main` function only once when the application is first started. Why would re-instantiating the `Class1` object affect the cache for `AppDAO`? – EliSquared Mar 07 '22 at 21:40
  • Oops. Misread that. Updated analysis above. – Dave W. Smith Mar 08 '22 at 01:24
  • @davidism If you're going to close this as relating to thread safety, please leave a hint as to why/where that applies here. The issue at hand is the behavior of an lru cache in the presence of _serial_ requests. – Dave W. Smith Mar 08 '22 at 04:04
  • Actually I figured out the problem, it was that I was re-instantating the `AppDAO` because `Class1` was designed differently than the other Classes in my app routes. I figured it out by printing the `self` argument in the cached call. – EliSquared Mar 08 '22 at 15:56