Ok, the animated spirograph more or less works as desired. But, because of how it is currently implemented only one person could use it successfully. If a second user stepped in, things would get pretty messed up. One user overwriting the files of the other user(s). So, need some way to track users and there specific file name.

Didn’t really want to get into user login/authentication. So going to first try using sessions. Expect the code will get rather messy, but—need to give it a try.

Basic Sessions

Flask uses client-side sessions. I.E. cryptographically-signed cookies. These cookies are viewable by anyone via a browser. So no sensitive data should be stored in these sessions. There are packages/modules offering server-side sessions, but I don’t think we really need that here.

Since the session cookies will be cryptgraphically signed we will need a secret key. I am going to store this key in a .env file. Retrieving it when the application starts. We will need to add another module/pacakge: python-dotenv to our environment. Since I use conda environments, I ran conda install python-dotenv in a terminal with the appropriate conda environment activated.

I added a .env file in the root directory of the Flask app. Then generated a guid:

PS R:\learn\py_play\sp_fa> [guid]::NewGuid()

Guid
----
e......

And added the line sess_key: = e..... to the .env file. In main.py, added some more imports (python os, dotenv, flask sesion), then loaded the .env file and set a variable for the session key.

import base64, math, pathlib, time, os
from dotenv import load_dotenv
... ...
from flask import Flask, render_template, request, session
... ...
app = Flask(__name__)
load_dotenv()
SESS_KEY = os.getenv('sess_key')

Okay, so far so good. Now let’s create a session and see what we get. To set up sessions, we really only need to initialize app.secert_key after importing flask.session. So, after the above code, I added app.secret_key = SESS_KEY.

Now, you need to know that you can only set, read or delete a session value within the context of a request. So to make that somewhat less onerous, I am adding a function to main.py. Was going to add it to sp_app_lib.py but wasn’t sure how that would work. For now this is just a stub of the function I will need. But, I want to go slowly and test things as I go.

def set_fileId():
  if 'fileId' not in session:
    session['fileId'] = 'a'

The function is then called at the top of the animation route.

@app.route('/spirograph/ani_basic', methods=['GET', 'POST'])
def ani_basic():
  global ax
  set_fileId()

And, I now use the session data to set the file name to which to save the animation.

    uv_nm = f"usr_{session['fileId']}_{nbr_f}.mp4"

And, after restarting the app, things seem to work just fine. Using the web developer tools in the browser, the iframe tag for a displayed video looks like this.

<iframe src="/static/usr_a_129.mp4?v=646648" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in- 
    picture" allowfullscreen="" style="width:720px; height: 720px;" frameborder="0"></iframe>

The file id is indeed set to ‘a’. In the browser I see the following:

session: "eyJmaWxlSWQiOiJhIn0.Y9WzkA.noLvqkipEEJqX4fBuin0MfNFL_U"

Test in Production

Figured I should test what I’ve got so far in my production environment. Need to know if the lengthy file save is going to cause any issues. My free trial of Google AppEngine has expired and I can’t seem to upgrade to a paying account. So will be testing this on PythonAnywhere. If I have some serious troubles may pursue trying to upgrade AppEngine account.

Wow, was that a bit of a pain. Took me quite some time to sort everything out.

Add Library

Apparently adding python-dotenv to the requirements.txt file did little or no good. I had to from within my dashboard open a bash console. Then use a pip install to add the library to my working environment. Being very careful to use pip3.9 so as to match my Python version.

17:03 ~ $ pip3.9 install --user python-dotenv
Looking in links: /usr/share/pip-wheels
Collecting python-dotenv
  Downloading python_dotenv-0.21.1-py3-none-any.whl (19 kB)
Installing collected packages: python-dotenv
Successfully installed python-dotenv-0.21.1
17:03 ~ $ 

Get dotenv Working

As mentioned above dotenv is installed in my project’s conda environment. And, I import and use dotenv in my main application module, main.py. Well that doesn’t work on PythonAnywhere. I needed to import dotenv and load the .env file in the app’s *_wsgi.py file. I also imported os. The bits I added look like the following. project_home was already defined so I used it.

import sys, os
from dotenv import load_dotenv
... ...
project_folder = os.path.expanduser(project_home)  # adjust as appropriate
load_dotenv(os.path.join(project_folder, '.env'))

The SESS_KEY = os.getenv('sess_key') in ‘main.py’ now works.

Saving File to /static/

Another headache. Everything I read said all I had to do was configure my static directory. So on my app’s web tab, I added the following to the Static files table.

URL         Directory

/static/    /home/barkncats/mysite/static/

Well, no matter how I messed about, there was always a static/usr_a_129.mp4: No such file or directory error in the error log.

In my local code using a relative path worked just fine. Apparently that is not the case for the PythonAnywhere environment. So, I decided to use the full path. After getting the session key, I added the following near the top of main.py.

THIS_FOLDER = pathlib.Path(__file__).parent.absolute()

And in the route code, I proceeded as follows. Note, I didn’t at first have that / "static" / when generating the file’s path.

    uv_nm = f"usr_{session['fileId']}_{nbr_f}.mp4"

    f_pth = THIS_FOLDER / "static" / uv_nm
    writervideo = FFMpegWriter(fps=mpg_fps)

But, once I added the / "static" / bingo. First working animation on PythonAnywhere.

Done

I was hoping to carry on and expand the animation code to work for multiple users. But getting things to work on PythonAnywhere took a bit longer than I anticipated. So I think I will call it a day and consider this post finished.

I will tackle multiple users accessing the animation in the next post.

Until then: tap, tap, tap, …

Resources