Update: some more debugging info at the bottom of this post, which reveals something very screwy in the python state.
I have a module which imports, among other things, the django User object.
The import works fine, and the code loads. However, when you call a function in that module that uses the User object, it errors saying that User is a NoneType.
There are also a number of other imports, and some module level global variables which are also None by the time the function is called.
Oddly, this is only a problem in our staging environments (Ubuntu 12.04). It works fine locally, which probably most closely resembles staging with extra python packages for dev work. Also fine in production.
Has anyone come across this before, and have any ideas what might cause it?
Here's the code:
import urllib
import time
import urlparse
# Django imports
from django.db.models.signals import post_delete
from django.db import models
from django.contrib.auth.models import User
from backends.cache.dualcache import cache
# Piston imports
from managers import TokenManager, ConsumerManager
from signals import consumer_post_delete
KEY_SIZE = 18
SECRET_SIZE = 32
VERIFIER_SIZE = 10
CONSUMER_STATES = (
('pending', 'Pending'),
('accepted', 'Accepted'),
('canceled', 'Canceled'),
('rejected', 'Rejected')
)
def generate_random(length=SECRET_SIZE):
return User.objects.make_random_password(length=length)
class Consumer(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
key = models.CharField(max_length=KEY_SIZE)
secret = models.CharField(max_length=SECRET_SIZE)
status = models.CharField(max_length=16, choices=CONSUMER_STATES, default='pending')
objects = ConsumerManager()
def __unicode__(self):
return u"Consumer %s with key %s" % (self.name, self.key)
def generate_random_codes(self):
key = User.objects.make_random_password(length=KEY_SIZE)
secret = generate_random(SECRET_SIZE)
while Consumer.objects.filter(key__exact=key, secret__exact=secret).count():
secret = generate_random(SECRET_SIZE)
self.key = key
self.secret = secret
self.save()
and here's the work around, which means basically to import what you need again inside the function:
import urllib
import time
import urlparse
# Django imports
from django.db.models.signals import post_delete
from django.db import models
from django.contrib.auth.models import User
from backends.cache.dualcache import cache
# Piston imports
from managers import TokenManager, ConsumerManager
from signals import consumer_post_delete
KEY_SIZE = 18
SECRET_SIZE = 32
VERIFIER_SIZE = 10
CONSUMER_STATES = (
('pending', 'Pending'),
('accepted', 'Accepted'),
('canceled', 'Canceled'),
('rejected', 'Rejected')
)
def generate_random(length=SECRET_SIZE):
return User.objects.make_random_password(length=length)
class Consumer(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
key = models.CharField(max_length=KEY_SIZE)
secret = models.CharField(max_length=SECRET_SIZE)
status = models.CharField(max_length=16, choices=CONSUMER_STATES, default='pending')
objects = ConsumerManager()
def __unicode__(self):
return u"Consumer %s with key %s" % (self.name, self.key)
def generate_random_codes(self):
from piston.models import KEY_SIZE, SECRET_SIZE, Consumer
from django.contrib.auth.models import User
from piston.models import generate_random
key = User.objects.make_random_password(length=KEY_SIZE)
secret = generate_random(SECRET_SIZE)
while Consumer.objects.filter(key__exact=key, secret__exact=secret).count():
secret = generate_random(SECRET_SIZE)
self.key = key
self.secret = secret
self.save()
Here's the stack trace. The error is caused by the line:
key = User.objects.make_random_password(length=KEY_SIZE)
in the generate_random_codes function.
Traceback:
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/contrib/admin/options.py" in wrapper
366. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
89. response = view_func(request, *args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/contrib/admin/sites.py" in inner
196. return view(request, *args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapper
25. return bound_func(*args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/utils/decorators.py" in bound_func
21. return func(self, *args2, **kwargs2)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/db/transaction.py" in inner
224. return func(*args, **kwargs)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/contrib/admin/options.py" in add_view
970. form = ModelForm(initial=initial)
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/forms/models.py" in __init__
234. self.instance = opts.model()
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/db/models/base.py" in __init__
349. val = field.get_default()
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/db/models/fields/related.py" in get_default
983. field_default = super(ForeignKey, self).get_default()
File "/sites/tellybug/shared/webserver/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py" in get_default
379. return self.default()
File "/sites/tellybug/releases/b92109dd526607b2af92ad6b7f494f3f06e31bb2/webserver/tellybug/tbapp/models/tellybugapp.py" in generate_new_consumer
11. consumer.generate_random_codes()
File "/sites/tellybug/releases/b92109dd526607b2af92ad6b7f494f3f06e31bb2/webserver/tellybug/piston/models.py" in generate_random_codes
57. key = User.objects.make_random_password(length=KEY_SIZE)
Exception Type: AttributeError at /admin/tbapp/tellybugapp/add/
Exception Value: 'NoneType' object has no attribute 'objects'
Update: It's not something just deleting the User object - something is wrecking the entire context in the function.
def generate_random_codes(self):
"""
Used to generate random key/secret pairings. Use this after you've
added the other data in place of save().
c = Consumer()
c.name = "My consumer"
c.description = "An app that makes ponies from the API."
c.user = some_user_object
c.generate_random_codes()
"""
import sys
print "Globals", globals()
print "Name ", __name__
print "Package ", __package__
print "Sys modules", sys.modules['piston.models'].__dict__
key = User.objects.make_random_password(length=KEY_SIZE)
With these print statements, the output is:
Globals {'ColumnFamilyMap': None, 'datetime': None, 'KEY_SIZE': None, 'TokenManager': None, 'ConsistencyLevel': None, 'Nonce': None, 'uuid': None, 'cache': None, 'urllib': None, '__package__': None, 'models': None, 'User': None, .... }
Name None
Package None
Sys modules {'ColumnFamilyMap': <class 'pycassa.columnfamilymap.ColumnFamilyMap'>, 'datetime': <type 'datetime.datetime'>, 'KEY_SIZE': 18, 'NonceType': <class 'piston.models.NonceType'>, 'OAuthToken': <class 'piston.models.OAuthToken'>, 'TokenManager': <class 'piston.managers.TokenManager'>, 'ConsistencyLevel': <class 'pycassa.cassandra.ttypes.ConsistencyLevel'>, 'Nonce': <class 'piston.models.Nonce'>, 'uuid': <module 'uuid' from '/usr/lib/python2.7/uuid.pyc'>, ...}
Note that both __package__
and __name__
are undefined, which I thought was pretty much impossible, and that while the sys.modules version of the module has a correct __dict__
, the return value from globals()
is nonsense.
This happens to a function in an imported module that is still executing after that module is garbage collected.
Since your code isn't enough to reproduce the issue, here's a simplified example that shows the behaviour. Create a file containing the following and import it either from the Python command line or from another file. It doesn't work if you just run it at the top level.
import sys
import threading
x = "foo"
def run():
while True:
print "%s %s\n" % (sys, x)
threading.Thread(target = run).start()
sys.stdin.readline()
Running it:
$ python
>>> import evil_threading
<module 'sys' (built-in)> foo
<module 'sys' (built-in)> foo
... press Ctrl-C
None None
None None
... press Ctrl-\ to kill the Python interpreter
During Python shutdown, modules are set to None
. This is an obscure Python behaviour that was removed in 3.4.
In this example, terminating the main thread results in shutdown, but the other thread is still running, so it sees the modules as None
.
There is a simpler example from here which does the same thing by deleting the module reference directly from sys.modules.
import sys
print sys
del sys.modules['__main__']
print sys
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With