Reputation:
Flask's session
object appears to be changing an OrderedDict
into a dict
. This matters because I had hoped to store information regarding the current user's accessible pages in an OrderedDict
to assist with automating the creation of the navigation bar.
I could be sleepy-eyed but I don't think I've written anything here that would overwrite existing data, so AFAICT it just converts to dict
for no apparent reason.
Here's the code that sets the OrderedDict
in the session
object.
def safe_route(page_request):
"""Returns the correct page for the user's request.
If a user requests a non-existent page, 404.
If user wants a page they shouldn't see, but somehow
they know about it, redirects to an access-denied page.
If everything is in order, returns the page the user requests.
"""
if page_request not in ordered_urls:
return abort(404) # early return for 404s
# get current user and build their navi-dict.
# this dict will be used to build the navibar in the webpage.
if not 'registered' in session:
CurrentUser = get_user(request)
session['name'] = CurrentUser.name
session['navi'] = build_user_navi(CurrentUser)
session['registered'] = True
if page_request in session['navi']:
tpl = "/{}.html".format(page_request)
else:
# if here, the user requested a page that DOES exist
# but they do NOT have access to. therefore, they are being
# goons and require a trip to boxxytown
tpl = "accessdenied.html"
return render_template(tpl, on_page=page_request)
It checks to ensure that the session has the 'registered'
flag; if not it gets the user information and then calls build_user_navi
which returns the OrderedDict
. This works the first time; the initial page load shows the links in the navibar as I originally intended. Upon subsequent clicking around, the order is lost and apparently so is the type on the OrderedDict
.
Before the render_template
call, I added this line in the code to see if it was just changing types on me:
print type(session['navi'])
Sure enough, it switches between <class 'collections.OrderedDict'>
and <type 'dict'>
.
Is there a method for correctly storing an OrderedDict
as session data, or is there some Perfectly Good Reason why this would only work the first time which should dissuade me from this approach?
Upvotes: 3
Views: 770
Reputation: 127340
All data in the session needs to be JSON serializable. An OrderedDict
is serializable because it is a subclass of dict
, but there is no ordered dict in JSON, so you lose order during serialization.
You can override how Flask serializes the session in order to support ordered dicts. It's not the most convenient process, since you have to copy the code for the class.
from base64 import b64encode, b64decode
from collections import OrderedDict
from datetime import datetime
import uuid
from flask import json
from flask._compat import text_type, iteritems
from flask.debughelpers import UnexpectedUnicodeError
from markupsafe import Markup
from werkzeug.http import http_date, parse_date
class TaggedJSONSerializer(object):
def dumps(self, value):
def _tag(value):
if isinstance(value, tuple):
return {' t': [_tag(x) for x in value]}
elif isinstance(value, uuid.UUID):
return {' u': value.hex}
elif isinstance(value, bytes):
return {' b': b64encode(value).decode('ascii')}
elif callable(getattr(value, '__html__', None)):
return {' m': text_type(value.__html__())}
elif isinstance(value, list):
return [_tag(x) for x in value]
elif isinstance(value, datetime):
return {' d': http_date(value)}
elif isinstance(value, OrderedDict):
return {'OrderedDict': [[k, _tag(v)] for k, v in iteritems(value)]}
elif isinstance(value, dict):
return dict((k, _tag(v)) for k, v in iteritems(value))
elif isinstance(value, str):
try:
return text_type(value)
except UnicodeError:
raise UnexpectedUnicodeError(u'A byte string with '
u'non-ASCII data was passed to the session system '
u'which can only store unicode strings. Consider '
u'base64 encoding your string (String was %r)' % value)
return value
return json.dumps(_tag(value), separators=(',', ':'))
def loads(self, value):
def object_hook(obj):
if len(obj) != 1:
return obj
the_key, the_value = next(iteritems(obj))
if the_key == ' t':
return tuple(the_value)
elif the_key == ' u':
return uuid.UUID(the_value)
elif the_key == ' b':
return b64decode(the_value)
elif the_key == ' m':
return Markup(the_value)
elif the_key == ' d':
return parse_date(the_value)
elif the_key == 'OrderedDict':
return OrderedDict(the_value)
return obj
return json.loads(value, object_hook=object_hook)
Here's a demonstration of the new tag in action. The OrderedDict is deserialized correctly.
s = TaggedJSONSerializer()
data = OrderedDict((c, i) for i, c in enumerate('abcd'))
print(data) # OrderedDict([('a', 0), ('b', 1), ('c', 2), ('d', 3)])
data = s.dumps(data)
print(data) # {"OrderedDict":[["a",0],["b",1],["c",2],["d",3]]}
data = s.loads(data)
print(data) # OrderedDict([('a', 0), ('b', 1), ('c', 2), ('d', 3)])
Then tell your app to use this serializer.
app.session_interface.serializer = TaggedJSONSerializer()
Upvotes: 2
Reputation: 40894
My suspicion is that session object is deep-copied somewhere, and the OrderedDict
gets copied as a plain dict
. This can be found out by e.g. consulting the source.
The simplest fix I'd come up with is to store a plain list of key-value pairs instead of an OrderedDict
, which is about as easy to iterate over and is easy to convert to any king of dict
should you need repeated fast lookup.
Upvotes: 1