Don’t know if this will end up being published; but I thought I’d at least document changes I made today (2023.11.23, long before the scheduled publish date).

If a visitor uses the curve parameter specification form they have the option of choosing to have a random selection of wheel types used to generate the spirograph. If Draw Another One (or the image on the front page) is clicked the app always generates the image using a single wheel type. I thought I’d like to change that. It would make for some different spirograph images.

Random Wheel Types

I figured I would used random wheel types about a ΒΌ of the time. It looked to me that the logical place to add the code would be in the get_curve_data() function in the main module. (Well in the main module for now.)

So before processing the form data, I added some code to convince the app to use a random selection of wheel types.

def get_curve_data(i_type, f_data, p_pts):
  # if not do_rpt or u_agn and doing a draw it again type submission, 25% time use random wheel shapes
  if not g.do_rpt and 'u_agn' not in f_data:
    if 'shape' not in f_data and not f_data['shp_mlt']:
      mlt_chk = rng.integers(0, 4)
      if mlt_chk == 3:
        f_data['shp_mlt'] = 'rnd'
  sal.proc_curve_form(f_data, i_type)

Bug #1

And, that, when the code to modify the shp_mlt field ran, unfortunately resulted in an error being thrown.

Traceback (most recent call last):
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2091, in __call__
    return self.wsgi_app(environ, start_response)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2076, in wsgi_app
    response = self.handle_exception(e)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1519, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1517, in full_dispatch_request
    rv = self.dispatch_request()
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1503, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "R:\learn\py_play\sp_fa\main.py", line 507, in sp_clw_btw
    t_xs, t_ys, *_ = get_curve_data('clw_btw', request.form, 2048)
  File "R:\learn\py_play\sp_fa\main.py", line 46, in get_curve_data
    f_data['shp_mlt'] = 'rnd'
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\werkzeug\datastructures.py", line 146, in __setitem__
    is_immutable(self)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\werkzeug\datastructures.py", line 20, in is_immutable
    raise TypeError(f"{type(self).__name__!r} objects are immutable")
TypeError: 'ImmutableMultiDict' objects are immutable

I had not realized/known that the dictionary of form POST data was immutable or a special Flask data structure. But a pretty easy fix. I.E. get a mutable version.

def get_curve_data(i_type, f_data, p_pts):
  # if not do_rpt or u_agn and doing a draw it again type submission, 25% time use random wheel shapes
  if not g.do_rpt and 'u_agn' not in f_data:
    if 'shape' not in f_data and not f_data['shp_mlt']:
      mlt_chk = rng.integers(0, 4)
      if mlt_chk == 3:
        # make form data mutable dictionary
        f_data = f_data.to_dict()
        f_data['shp_mlt'] = 'rnd'
  sal.proc_curve_form(f_data, i_type)

But #2

And, that seemed to work. At least for a short while. Then

Traceback (most recent call last):
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2091, in __call__
    return self.wsgi_app(environ, start_response)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2076, in wsgi_app
    response = self.handle_exception(e)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1519, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1517, in full_dispatch_request
    rv = self.dispatch_request()
  File "E:\appDev\Miniconda3\envs\flk_3.10\Lib\site-packages\flask\app.py", line 1503, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "R:\learn\py_play\sp_fa\main.py", line 507, in sp_clw_btw
    t_xs, t_ys, *_ = get_curve_data('clw_btw', request.form, 2048)
  File "R:\learn\py_play\sp_fa\main.py", line 59, in get_curve_data
    t_xs, t_ys, t_shp = sal.init_curve()
  File "R:\learn\py_play\sp_fa\sp_app_lib.py", line 1285, in init_curve
    t_xs, t_ys, shape = splt.mk_rnd_curve(g.rds, su=shape)
  File "R:\learn\py_play\sp_fa\spiro_plotlib.py", line 490, in mk_rnd_curve
    t_x, t_y = shps[s_used[i]](r_wds[i], r_hts[i], t, sp_frq[i])
IndexError: list index out of range

Took me awhile to sort things out. But, basically once the code activated random wheel shapes, it continued to use them for all subsequent calls. But the list of wheel shapes never changed after that first instantiation. At some point the randomly generated number of wheels on any given request exceeded the list of wheel shapes saved in g.su. And, bingo, we get an IndexError.

Unfortunately because of my random approach to developing this app, I couldn’t really sort a good place to fix this issue. Well two issues:

  1. don’t continue using random wheel shapes after the first request (approximately 1 of 4 times)
  2. prevent IndexError being thrown

The first one I think would be best looked after in get_curve_data(). The latter issue I chose to look after in init_curve() in the spiro_app_lib module. Though that to me seems like the wrong place to do so. Something to refactor in future.

I also had to move the call to .to_dict() outside the if block as it would also be needed in the else block.

def get_curve_data(i_type, f_data, p_pts):
  if not g.do_rpt and 'u_agn' not in f_data:
    if 'shape' not in f_data and not f_data['shp_mlt']:
      # make form data mutable dictionary
      f_data = f_data.to_dict(flat=False)
      mlt_chk = rng.integers(0, 4)
      if mlt_chk == 3:
        # use random wheel shapes
        f_data['shp_mlt'] = 'rnd'
      else:
        # use single wheel shape
        f_data['shape'] = 'x'
        f_data['shp_mlt'] = ''
  sal.proc_curve_form(f_data, i_type)

Modified the appropriate else block to check and fix if needed.

def init_curve():
  ... ...
  if len(g.su) == 1:
    t_xs, t_ys = splt.mk_curve(g.rds, shp=g.su)
    shape = f"{splt.shp_nm[g.su].lower()} ({g.su})"
  else:
    # check that have enough shapes for the number of wheels
    # fix if necessary
    s_cnt = len(g.su)
    if s_cnt < g.n_whl:
      shape = [g.su[i % s_cnt] for i in range(g.n_whl)]
    else:
      shape = g.su
    t_xs, t_ys, shape = splt.mk_rnd_curve(g.rds, su=shape)

And that seems to have worked. After repeated requests, no errors thrown and the spirographs drawn go back and forth between single wheel and random wheel types (though not equally of course).

Here’s an example with 8 wheels (shape(s): [’s’, ’t’, ’s’, ‘r’, ’s’, ‘r’, ’s’, ‘q’]).

cycling line width (colour between) using 8 wheels of differing shapes
CLW (BTW) using 8 wheels of differing shapes

Done

Short, sweet and to the point. Think that’s a good way to leave it.

Until next time, may your code be to the point; as well as short and sweet.

(And, yes, I know that I likely really need to do more refactoring.)

Resources