Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Imported modules become None when running a function

Tags:

python

django

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.

like image 976
Stuart Marsh Avatar asked Jun 13 '13 10:06

Stuart Marsh


1 Answers

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
like image 176
Ben C Avatar answered Sep 30 '22 10:09

Ben C