Reputation: 29
I am planning to deploy a program via waitress for development purposes. I decided to use Flask Session to be able to create a unique identity per user.
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_FILE_DIR'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'session_files')
Session(app)
@app.route('/upload', methods=['POST'])
def upload():
files = request.files.getlist('files')
if files:
if 'user_folder' not in session:
session['user_folder'] = str(uuid.uuid4())
user_upload_folder = os.path.join(app.config['UPLOAD_FOLDER'], session['user_folder'])
os.makedirs(user_upload_folder, exist_ok=True)
else:
user_upload_folder = os.path.join(app.config['UPLOAD_FOLDER'], session['user_folder'])
The problem with the code is that when I went and ran waitress and tested the code, I saw in the designated folder that the multiple files uploaded were separated into different folders when I am only expecting one unique folder per each instance. Does it help that I also checked the root of ['SESSION_FILE_DIR'] and I noticed there were two different files created. Any solutions would be great
EDIT
Code I used to run the flask program
if __name__ == '__main__':
#serve(app, host='127.0.0.1', port=8080)
app.run(debug=True, use_reloader=False)
I used waitress serve() to run the program but for debugging purposes I use the app.run().
EDIT 2
Here is my updated code when I attempted to implement redis along with waitress
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['SESSION_TYPE'] = 'redis'
redis_client = redis.Redis()
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
app.config['SESSION_COOKIE_SECURE'] = True
Session(app)
def upload():
pdf_files = request.files.getlist('pdf_files')
if pdf_files:
if not redis_client.exists('user_folder:' + session.sid):
user_folder = str(uuid.uuid4())
user_upload_folder = os.path.join(app.config['UPLOAD_FOLDER'], user_folder)
D_user_upload_folder = user_upload_folder
os.makedirs(user_upload_folder, exist_ok=True)
# Store the user folder in Redis
test = redis_client.set('user_folder:' + session.sid, user_upload_folder)
print("Filename: ", test)
else:
user_upload_folder = redis_client.get('user_folder:' + session.sid)
D_user_upload_folder = user_upload_folder.decode()
#user_upload_folder = os.path.join(app.config['UPLOAD_FOLDER'], user_folder)
for pdf_file in pdf_files:
filename = secure_filename(pdf_file.headers['Content-Disposition'].split('filename=')[1].strip('\"'))
file_path = os.path.join(D_user_upload_folder, filename)
pdf_file.save(file_path)
#Expermienting checking maybe the subprocesses were uploading the files simultaneously
if len(pdf_files) == 1:
return jsonify({'message': 'File uploaded successfully.'}), 200
else:
# Call the upload function again for the remaining files
remaining_files = pdf_files[1:]
with app.test_client() as client:
client.post('/upload', data={'pdf_files': remaining_files})
The code seemed to work fine on the local machine but when I tested it on a remote computer. Multiple folders were created again per each file
Upvotes: 0
Views: 135
Reputation: 29
So, I have already managed to solve my problem.
Redis is part of the solution as it allows the data I have to be synchronized when used in other computers.
But it does not stop the problem of waitress making multiple calls in one function which is the root cause and reason why every time I upload a file multiple folders are created.
To solve this problem I first made sure that the creation of the folder is called during the creation of the main html page (In my case it is called "index()")
@app.route('/')
def index():
global current_folder
create_user_folder()
return render_template('index.html', filenames=[])
I created a global variable just to save me the hassle of passing parameters of the current name of the folder.
Since waitress creates multiple workers with each of their own unique identities. I used a thread-local variable to check if the thread has already executed the function.
import threading
# Create a thread-local variable to store whether the function has been executed for the current user within the thread
thread_local = threading.local()
def create_user_folder():
global current_folder
# Check if the function has already been executed for the current user in the current thread
if hasattr(thread_local, 'user_folder'):
session['user_folder'] = thread_local.user_folder
return
'''
More code that checks if there is user_id and folder already created
if not create the folder and assign them to their respected variables.
'''
This allowed me, to check if the function has already been executed and along with making the execution of this function at the initial creation of the main page, it has allowed me to prevent the multiple workers from creating multiple folders when called.
Upvotes: 0
Reputation: 112
The problem lies in the fact that Waitress, the WSGI server you're using, can spawn multiple worker processes to handle incoming requests. Each worker process has its own memory space and session storage, which can result in multiple folders being created for the same session. Try using redis:
import redis
....
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
Upvotes: 0