Modifying Dictionary in Django Session Does Not Modify Session - django

I am storing dictionaries in my session referenced by a string key:
>>> request.session['my_dict'] = {'a': 1, 'b': 2, 'c': 3}
The problem I encountered was that when I modified the dictionary directly, the value would not be changed during the next request:
>>> request.session['my_dict'].pop('c')
3
>>> request.session.has_key('c')
False
# looks okay...
...
# Next request
>>> request.session.has_key('c')
True
# what gives!

As the documentation states, another option is to use
SESSION_SAVE_EVERY_REQUEST=True
which will make this happen every request anyway. Might be worth it if this happens a lot in your code; I'm guessing the occasional additional overhead wouldn't be much and it is far less than the potential problems from neglecting from including the
request.session.modified = True
line each time.

I apologize for "asking" a question to which I already know the answer, but this was frustrating enough that I thought the answer should be recorded on stackoverflow. If anyone has something to add to my explanation I will award the "answer". I couldn't find the answer by searching based on the problem, but after searching based upon the answer I found that my "problem" is documented behavior. Also turns out another person had this problem.
It turns out that SessionBase is a dictionary-like object that keeps track of when you modify it's keys, and manually sets an attribute modified (there's also an accessed). If you mess around with objects within those keys, however, SessionBase has no way to know that the objects are modified, and therefore your changes might not get stored in whatever backend you are using. (I'm using a database backend; I presume this problem applies to all backends, though.) This problem might not apply to models, since the backend is probably storing a reference to the model (and therefore would receive any changes when it loaded the model from the database), but the problem does apply to dictionaries (and perhaps any other base python types that must be stored entirely in the session store.)
The trick is that whenever you modify objects in the session that the session won't notice, you must explicitly tell the session that it is modified:
>>> request.session.modified = True
Hope this helps someone.
The way I got around this was to encapsulate any pop actions on the session into a method that takes care of the details (this method also accepts a view parameter so that session variables can be view-specific):
def session_pop(request, view, key, *args, **kwargs):
"""
Either returns and removes the value of the key from request.session, or,
if request.session[key] is a list, returns the result of a pop on this
list.
Also, if view is not None, only looks within request.session[view.func_name]
so that I can store view-specific session variables.
"""
# figure out which dictionary we want to operate on.
dicto = {}
if view is None:
dicto = request.session
else:
if request.session.has_key(view.func_name):
dicto = request.session[view.func_name]
if dicto.has_key(key):
# This is redundant if `dicto == request.session`, but rather than
# duplicate the logic to test whether we popped a list underneath
# the root level of the session, (which is also determined by `view`)
# just explicitly set `modified`
# since we certainly modified the session here.
request.session.modified = True
# Return a non-list
if not type(dicto[key]) == type(list()):
return dicto.pop(key)
# pop a list
else:
if len(dicto[key]) > 0:
return dicto[key].pop()
# Parse out a default from the args/kwargs
if len(args) > 0:
default = args[0]
elif kwargs.has_key('default'):
default = kwargs['default']
else:
# If there wasn't one, complain
raise KeyError('Session does not have key "{0}" and no default was provided'.format(key))
return default

I'm not too surprised by this. I guess it's just like modifying the contents of a tuple:
a = (1,[2],3)
print a
>>> 1, [2], 3)
a[1] = 4
>>> Traceback (most recent call last):
... File "<stdin>", line 1, in <module>
... TypeError: 'tuple' object does not support item assignment
print a
>>> 1, [2], 3)
a[1][0] = 4
print a
>>> 1, [4], 3)
But thanks anyway.

Related

Unexpected results when checking all values in a Python dictionary are None

Let's consider the following three dictionaries:
topByClass = {'Real Estate': 'VNO', 'Construction': 'TOL', 'Utilities': 'EXC'}
shouldPass = {'Real Estate': None, 'Construction': None, 'Utilities': 'EXC'}
shouldFail = {'Real Estate': None, 'Construction': None, 'Utilities': None}
I am looking to separate instances where all values in the dictionary are None, from everything else. (i.e. the first two should pass, while the last should fail)
I looked around online, particularly at posts such as this one. I tested various solutions out in the python console (running Python 2.7 in a virtualenv on my Mac), and the following worked:
not all(value == None for value in topByClass.values())
Both with and without "not" I can separate dictionaries like 'topByClass' from 'shouldFail'.
>>> not all(value == None for value in shouldFail.values())
>>> False
>>> not all(value == None for value in topByClass.values())
>>> True
(and vice versa for without not)
The thing is, when I go to run the python file, the if statement always evaluates as if every value is None. I have checked if possibly I am mistaking the dictionary, however I print off the dict "topByClass" in the console, and have directly pasted it above. Any ideas what this could be?
Edit:
def _getTopByClass(self, assetClass):
# Find the instrument with the highest rank.
ret = None
highestRank = None
for instrument in self.__instrumentsByClass[assetClass]:
rank = self._getRank(instrument)
if rank is not None and (highestRank is None or rank > highestRank):
highestRank = rank
ret = instrument
return ret
def _getTop(self):
ret = {}
for assetClass in self.__instrumentsByClass:
ret[assetClass] = self._getTopByClass(assetClass)
return ret
def _rebalance(self):
topByClass = self.getTop()
self.info(topByClass) # where I get the output I presented above
if any(value is not None for value in topByClass.values()):
self.info("Not All Empty")
else:
self.info("All None")
Now with the above "if" all are printing ("Not All Empty")
If you would like to see getRank() or more, I would reccommend this example from PyAlgoTrade, as the core mechanics affecting the problem are similar.
Edit 2:
Thought I might mention this incase someone tried to replicate the file linked above... PyAlgoTrade's modules for downloading feeds doesn't work. So you have to use this package to download the data, as well as adding bars from csv:
feed = yahoofeed.Feed()
feed.addBarsFromCSV("SPY", "data/SPY.csv")
for industry, stocks in instrumentsByClass.items():
for stock in stocks:
feed.addBarsFromCSV(stock, "data/"+stock+".csv")
Edit 3: Added some debug info:
self.info(isinstance(topByClass, dict))
self.info(isinstance(topByClass.values(), list))
self.info(isinstance(topByClass.values()[0], str))
returns:
>>> True
>>> True
>>> True (False when the first value is None)
Also, per a comment I thought I'd throw this in
self.info(list(topByClass.values()))
>>> [None, None, None, None]
FINAL EDIT:
Many thanks to all the people who responded, thought I would go ahead and post what I figured out incase anyone runs into a similar problem...
First of all the code/output that identified the problem:
self.info(list(shouldFail.values())
>>> [None, None, None]
self.info(list(topByClass.values())
>>>['VNO', 'TOL', 'EXC']
self.info(list(value is not None for value in topByClass.values()))
>>> [True, True, True]
self.info(any(value is not None for value in topByClass.values()))
>>> <generator object <genexpr> at 0x116094dc0>
I wasn't sure why it returned a generator, then I realized that it was probably using numpy's any() function, as I decalred:
import numpy as *
After changing this to:
import numpy as np
it behaved as expected.
Since you haven't shown us the actual code that is tickling the fail (I understand that might not be possible in a production environment), here is some philosophy about how to debug in a class hierarchy, and one theory about what might be causing this:
do add a print or logging statement to print/log your instance's values before you test it. Then you can see if it really held the value you believe it did ("When reality collides with a theory, reality wins"). Logging should become your new trusty friend in bug-hunting. Distrust all your assumptions (rubber-duck them). But logging is faster and more reliable than poring over a large class hierarchy.
beware there might be an accidental string conversion somewhere up your class hierarchy (possibly from some class someone else wrote, or accidental use of str or repr e.g in a constructor, setter or property, or a init or method with an arg default = 'None' instead of None): 'None' != None . This sort of bug is subtle and insidious. If you find it, you will laugh as you cry.
Anyway, happy logging, and please post us the logger output when you pinpoint the failing comparison. It's important to track down these sort of 'existential' bugs, since they reveal something broken or blind spot in your chain of assumptions, or debugging methodology. That's how you learn.

django orm: Check if obj is in queryset

How can I check whether an obj is in a queryset or not?
I tried this:
self.assertIn(obj, list(MyModel.objects.filter(...))
But it does not work in my case.
AssertionError: <MyModel 137 'unclassified'> not found in
[<MyModel 1676 'foo'>, ..., <MyModel 137 'unclassified'>, ...]
I don't understand it, since it is in the list.
How about
self.assertTrue(MyModel.filter(...).filter(pk=obj.pk).exists())
First of all, it should be MyModel.objects.filter(...). If you omit the .objects, you should've gotten a different error, so I'm assuming you did include it but just forgot it in the question.
If obj is actually in the QuerySet returned, what you did should have worked as Django model instances provides an equal comparator which compares both the type and the primary key. list() is not required around the QuerySet, though it should still work if you used it.
From the Django 1.5 source:
def __eq__(self, other):
return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
If it still doesn't work, there are a few possible causes:
The type doesn't match. In this case, it doesn't matter even if the object pk exists in the QuerySet's object pks. (You cannot compare apples to oranges)
There is no such object in the database (i.e. it hasn't been saved yet)
The type matches but the object is not in the QuerySet (i.e. filtered out)
You overrode the __eq__ method and did something weird. Or you overrode the default manager .objects with some custom filter. This scenario is outside the scope of this answer and if you did this, you should probably know how to fix it.
To help you diagnose which is the case, try this:
self.assertTrue(isinstance(obj, MyModel))
# 1. If it fails here, your object is an incorrect type
# Warning: the following tests can be very slow if you have a lot of data
self.assertIn(obj.pk, MyModel.objects.values_list('pk', flat=True))
# 2. If it fails here, the object doesn't exist in the database
self.assertIn(obj.pk, MyModel.objects.filter(...).values_list('pk', flat=True))
# 3. If it fails here, the object did not pass your filter conditions.
self.assertIn(obj, MyModel.objects.filter(...))
# 4. If it fails here, you probably messed with the Django ORM internals. Tsk tsk.
Just with, note the .all()
queryset_result = MyModel.filter(...).all()
if obj in queryset_result:
//obj is in the queryset
The "in" fails because the objects aren't actually equal, as equality is object identity by default. If you want "in" to work, you'd have to implement __eq__ accordingly on your model.
If you don't want to do that, you can check by comparing the pk, like so
self.assertIn(obj.pk, [o.pk for o in MyModel.filter(...)])
I think This is very simple way get to find out object present in queryset or not.
First Example:
obj_list = MyModel.filter(...)
if obj in obj_list:
print "obj in queryset"
else:
print "not in queryset"
Second Example:
obj_list = MyModel.filter(...)
try:
obj_list.get(pk=obj.id)
except:
# If try get success obj is present in query set else your this except get executed.

Why does get_FOO_display() return integer value when logging info (django)?

Why does get_FOO_display() return integer value when logging info (django)?
I have a model field that is using a choice to restrict its value. This works fine
and I have it working everywhere within the app, except when logging information,
when the get_FOO_display() method returns the underlying integer value instead
of the human-readable version.
This is the model definition (abridged):
THING_ROLE_MONSTER = 0
THING_ROLE_MUMMY = 1
ROLE_CHOICES = (
(THING_ROLE_MONSTER, u'Monster'),
(THING_ROLE_MUMMY, u'Mummy'),
)
# definition of property within model
class Thing(models.Model):
...
role = models.IntegerField(
'Role',
default=0,
choices=ROLE_CHOICES
)
If I run this within the (django) interactive shell it behaves exactly as you would expect:
>>> from frankenstein.core.models import Thing
>>> thing = Thing()
>>> thing.role = 0
>>> thing.get_role_display()
u'Monster'
However, when I use exactly the same construct within a string formatting / logging
scenario I get the problem:
logger.info('New thing: <b>%s</b>', thing.get_role_display())
returns:
New thing: <b>0</b>
Help!
[UPDATE 1]
When I run the logging within the interactive shell I get the correct output:
>>> from frankenstein.core.models import Thing
>>> import logging
>>> thing = Thing()
>>> thing.role = 0
>>> logging.info('hello %s', b.get_role_display())
INFO hello Monster
[UPDATE 2] Django internals
Following up on the answer from #joao-oliveira below, I have dug into the internals and uncovered the following.
The underlying _get_FIELD_display method in django.db.models looks like this:
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)
If I put a breakpoint into the code, and then run ipdb I can see that I have the issue:
ipdb> thing.get_role_display()
u'1'
ipdb> thing._get_FIELD_display(thing._meta.get_field('role'))
u'1'
So, the fix hasn't changed anything. If I then try running through the _get_FIELD_display method code by hand, I get this:
ipdb> fld = thing._meta.get_field('role')
ipdb> fld.flatchoices
[(0, 'Monster'), (1, 'Mummy')]
ipdb> getattr(thing, fld.attname)
u'1'
ipdb> value = getattr(thing, fld.attname)
ipdb> dict(fld.flatchoices).get(value, value)
u'1'
Which is equivalent to saying:
ipdb> {0: 'Monster', 1: 'Mummy'}.get(u'1', u'1')
u'1'
So. The problem we have is that the method is using the string value u'1' to look up the corresponding description in the choices dictionary, but the dictionary keys are integers, and not strings. Hence we never get a match, but instead the default value, which is set to the existing value (the string).
If I manually force the cast to int, the code works as expected:
ipdb> dict(fld.flatchoices).get(int(value), value)
'Mummy'
ipdb> print 'w00t'
This is all great, but doesn't answer my original question as to why the get_foo_display method does return the right value most of the time. At some point the string (u'1') must be cast to the correct data type (1).
[UPDATE 3] The answer
Whilst an honourable mention must go to Joao for his insight, the bounty is going to Josh for pointing out the blunt fact that I am passing in the wrong value to begin with. I put this down to being an emigre from 'strongly-typed-world', where these things can't happen!
The code that I didn't include here is that the object is initialised from a django form, using the cleaned_data from a ChoiceField. The problem with this is that the output from a ChoiceField is a string, not an integer. The bit I missed is that in a loosely-typed language it is possible to set an integer property with a string, and for nothing bad to happen.
Having now looked into this, I see that I should have used the TypedChoiceField, to ensure that the output from cleaned_data is always an integer.
Thank you all.
I'm really sorry if this sounds condescending, but are you 100% sure that you're setting the value to the integer 1 and not the string '1'?
I've gone diving through the internals and running some tests and the only way that the issue you're experiencing makes sense is if you're setting the value to a string. See my simple test here:
>>> from flogger.models import TestUser
>>> t = TestUser()
>>> t.status = 1
>>> t.get_status_display()
u'Admin'
>>> t.status = '1'
>>> t.get_status_display()
u'1'
Examine your view code, or whatever code is actually setting the value, and examine the output of the field directly.
As you pasted from the internal model code:
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)
It simply gets the current value of the field, and indexes into the dictionary, and returns the value of the attribute if a lookup isn't found.
I'm guessing there were no errors previously, because the value is coerced into an integer before being inserted into the database.
Edit:
Regarding your update mentioning the type system of python. Firstly, you should be using TypedChoiceField to ensure the form verifies the type that you expect. Secondly, python is a strongly typed language, but the IntegerField does its own coercing with int() when preparing for the database.
Variables are not typed, but the values within them are. I was actually surprised that the IntegerField was coercing the string to an int also. Good lessen to learn here - check the basics first!
Haven't tried your code, neither the #like-it answer sorry, but _get_FIELD_display from models.Model is curried in the fields to set the get_Field_display function, so thats probably why you'r getting that output
try calling the _get_FIELD_display:
logging.info('hello %s', b._get_FIELD_display(b._meta.get('role')))
try this:
class Thing(models.Model):
THING_ROLE_MONSTER = 0
THING_ROLE_MUMMY = 1
ROLE_CHOICES = (
(THING_ROLE_MONSTER, u'Monster'),
(THING_ROLE_MUMMY, u'Mummy'),
)
role = models.IntegerField('Role', default=0,choices=ROLE_CHOICES)

Testing a session variable

I came across Django request.session; I know how to set and test it for a specific value.
request.session['name'] = "dummy"
and somewhere I check
if request.session['name'] == "dummy" :
#do something
But, now I have to check whether the session variable was even set in the first place? I mean how can I check whether there exists a value in the request.session['name'] is set?
Is there a way to check it?
Treat it as a Python dictionary:
if 'name' in request.session:
print request.session['name']
How to use sessions: Django documentation: How to use sessions
get will do all the work for you. Check if the key exists, if not then the value is None
if request.session.get('name', None) == "dummy":
print 'name is = dummy'
Another way of doing this is put it in try.
In this the third example code snippet
if you apply del operation in a session variable it which does not exist, so it throws KeyError.
so check it like this.
try:
print(request.session['name'])
except KeyError:
print('name variable is not set')

Hierarchical cache in Django

What I want to do is to mark some values in the cache as related so I could delete them at once. For example when I insert a new entry to the database I want to delete everything in the cache which was based on the old values in database.
I could always use cache.clear() but it seems too brutal to me. Or I could store related values together in the dictionary and cache this dictionary. Or I could maintain some kind of index in an extra field in cache. But everything seems to complicated to me (eventually slow?).
What you think? Is there any existing solution? Or is my approach wrong? Thanks for answers.
Are you using the cache api? It sounds like it.
This post, which pointed me to these slides helped me create a nice generational caching system which let me create the hierarchy I wanted.
In short, you store a generation key (such as group) in your cache and incorporate the value stored into your key creation function so that you can invalidate a whole set of keys at once.
With this basic concept you could create highly complex hierarchies or just a simple group system.
For example:
class Cache(object):
def generate_cache_key(self, key, group=None):
"""
Generate a cache key relating them via an outside source (group)
Generates key such as 'group-1:KEY-your-key-here'
Note: consider this pseudo code and definitely incomplete code.
"""
key_fragments = [('key', key)]
if group:
key_fragments.append((group, cache.get(group, '1')))
combined_key = ":".join(['%s-%s' % (name, value) for name, value in key_fragments)
hashed_key = md5(combined_key).hexdigest()
return hashed_key
def increment_group(self, group):
"""
Invalidate an entire group
"""
cache.incr(group)
def set(self, key, value, group=None):
key = self.generate_cache_key(key, group)
cache.set(key, value)
def get(self, key, group=None):
key = self.generate_cache_key(key, group)
return cache.get(key)
# example
>>> cache = Cache()
>>> cache.set('key', 'value', 'somehow_related')
>>> cache.set('key2', 'value2', 'somehow_related')
>>> cache.increment_group('somehow_related')
>>> cache.get('key') # both invalidated
>>> cache.get('key2') # both invalidated
Caching a dict or something serialised (with JSON or the like) sounds good to me. The cache backends are key-value stores like memcache, they aren't hierarchical.