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:
- don’t continue using random wheel shapes after the first request (approximately 1 of 4 times)
- 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’]).
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
- class werkzeug.datastructures.MultiDict()