#20761 closed Bug (fixed)
UnboundLocalError: local variable 'sid' referenced before assignment when using get_or_create with a badly formed __init__
| Reported by: | Owned by: | loic84 | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The cause of this bug seems to be a little convoluted. I don't expect it to happen in normal use, but it still seems to be unexpected behaviour.
The smallest testcase I can create it with is:
class Test(models.Model):
def __init__(self, **kwargs):
import django.db
raise django.db.IntegrityError("oops")
The cause of the Integrity error isn't important. I was trying some stuff in a custom init that didn't end up working. The error I get is:
In [2]: models.Test.objects.get_or_create(id=1)
/env/local/lib/python2.7/site-packages/django/db/models/manager.pyc in get_or_create(self, **kwargs)
144
145 def get_or_create(self, **kwargs):
--> 146 return self.get_query_set().get_or_create(**kwargs)
147
148 def create(self, **kwargs):
/env/local/lib/python2.7/site-packages/django/db/models/query.pyc in get_or_create(self, **kwargs)
479 return obj, True
480 except IntegrityError as e:
--> 481 transaction.savepoint_rollback(sid, using=self.db)
482 exc_info = sys.exc_info()
483 try:
UnboundLocalError: local variable 'sid' referenced before assignment
The reason for this seems to be that the try: block in query.py:get_or_create expects the IntegrityError to be thrown when the new object is saved, not when it's created (line 476).
Change History (8)
comment:1 by , 12 years ago
| Component: | Uncategorized → Database layer (models, ORM) |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:2 by , 12 years ago
@claudep the try/except construct is there to catch a race condition.
[0e51d8eb66121b2558a38c9f4e366df781046873] changed the method to handle transaction better, however I don't like how any error would lead to a self.get().
I'd suggest the following snippet:
except DatabaseError as e:
transaction.savepoint_rollback(sid, using=self.db)
if isinstance(e, IntegrityError):
return self.get(**lookup), False
Refs #20463 #20429 #12579 #13906.
Edit: Probably shouldn't look at the ORM code at 4AM. The self.model(**params) part can/should be moved outside of the try/except construct, only the save logic needs to be there.
comment:4 by , 12 years ago
| Has patch: | set |
|---|---|
| Owner: | changed from to |
| Status: | new → assigned |
| Version: | 1.5 → master |
comment:5 by , 12 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
comment:8 by , 12 years ago
Oups, sorry for the wrong ticket number, the latest commit above refers to #20791
I guess that the
obj = self.model(**params)line (now in_create_object_from_paramsin master) should be outside the try/except construct.