I am trying to run tests of my models using pytest. I am having trouble with using mixer on a SQLite3 in ":memory:" database.
I have a model named Category that has a foreign key attribute called "created_by" which references my custom User model.
When assigning the referenced model of User to the category variable in mixer.blend() I get an exception that says that the User instance is not that of a UUID.
See my test below:
import pytest
from mixer.backend.django import mixer
from apps.API import models
pytestmark = pytest.mark.django_db
class TestCategory():
def test_model(self):
category = mixer.blend(models.Category)
assert category.pk == 1, 'Should create a Category instance'
This is very strange as this worked previously (about a week ago). Why can mixer not coerce the UUID from the primary key of my User object?
If anyone has any ideas I am all ears. Below I define my models and show a few stack traces. Hopefully, we can find some insight.
Here is the stack trace in pytest:
self = <django.db.models.fields.UUIDField: id>, value = <User: [email protected]>
def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
> return uuid.UUID(value)
venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2325:
self = <[AttributeError("'UUID' object has no attribute 'int'") raised in repr()] UUID object at 0x7feeebf18f98>, hex = <User: [email protected]>, bytes = None, bytes_le = None
fields = None, int = None, version = None
def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
int=None, version=None):
r"""Create a UUID from either a string of 32 hexadecimal digits,
a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
in little-endian order as the 'bytes_le' argument, a tuple of six
integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
the 'fields' argument, or a single 128-bit integer as the 'int'
argument. When a string of hex digits is given, curly braces,
hyphens, and a URN prefix are all optional. For example, these
expressions all yield the same UUID:
UUID('{12345678-1234-5678-1234-567812345678}')
UUID('12345678123456781234567812345678')
UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
UUID(bytes='\x12\x34\x56\x78'*4)
UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
'\x12\x34\x56\x78\x12\x34\x56\x78')
UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
UUID(int=0x12345678123456781234567812345678)
Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
be given. The 'version' argument is optional; if given, the resulting
UUID will have its variant and version set according to RFC 4122,
overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
"""
if [hex, bytes, bytes_le, fields, int].count(None) != 4:
raise TypeError('one of the hex, bytes, bytes_le, fields, '
'or int arguments must be given')
if hex is not None:
> hex = hex.replace('urn:', '').replace('uuid:', '')
E AttributeError: 'User' object has no attribute 'replace'
/usr/lib/python3.6/uuid.py:137: AttributeError
During handling of the above exception, another exception occurred:
self = <apps.API.tests.test_models.TestCategory object at 0x7feeef113a58>
def test_model(self):
> category = mixer.blend(models.Category)
apps/API/tests/test_models.py:13:
venv/lib/python3.6/site-packages/mixer/main.py:568: in blend
return type_mixer.blend(**values)
venv/lib/python3.6/site-packages/mixer/main.py:116: in blend
for name, value in defaults.items()
venv/lib/python3.6/site-packages/mixer/main.py:116: in <genexpr>
for name, value in defaults.items()
venv/lib/python3.6/site-packages/mixer/mix_types.py:222: in gen_value
return type_mixer.gen_field(field)
venv/lib/python3.6/site-packages/mixer/backend/django.py:270: in gen_field
return super(TypeMixer, self).gen_field(field)
venv/lib/python3.6/site-packages/mixer/main.py:193: in gen_field
return self.gen_value(field.name, field, unique=unique)
venv/lib/python3.6/site-packages/mixer/main.py:261: in gen_value
return self.get_value(field_name, value)
venv/lib/python3.6/site-packages/mixer/backend/django.py:218: in get_value
return self._get_value(name, value, field)
venv/lib/python3.6/site-packages/mixer/backend/django.py:233: in _get_value
value = field.scheme.to_python(value)
venv/lib/python3.6/site-packages/django/db/models/fields/related.py:874: in to_python
return self.target_field.to_python(value)
self = <django.db.models.fields.UUIDField: id>, value = <User: [email protected]>
def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
return uuid.UUID(value)
except (AttributeError, ValueError):
raise exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
> params={'value': value},
)
E django.core.exceptions.ValidationError: ["'[email protected]' is not a valid UUID."]
venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2330: ValidationError
See this last section of code in the above stack trace:
I found that if I change "value" in the try block to "value.id" that the exception is not thrown.
def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
return uuid.UUID(value)
except (AttributeError, ValueError):
raise exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
> params={'value': value},
)
E django.core.exceptions.ValidationError:
["'[email protected]' is not a valid UUID."]
My User model is using UUID as its primary key.
Here are the two models (Category and User) that I am using.
My abstract BaseModel inheritance in Category just includes basic information like created_date, is_active, etc.
Category:
# django
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
# models
from apps.API.models.BaseModel import BaseModel
class Category(BaseModel):
CATEGORIES = (
# choices tuples go here
)
category = models.CharField(_('category'),
choices=CATEGORIES,
max_length=80,
blank=False,
db_index=True
)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET(
BaseModel.get_sentinel_user),
blank=False,
editable=False,
db_index=True,
related_name=(
'created_categories')
)
class Meta:
app_label = "API"
verbose_name = _('Category')
verbose_name_plural = _('Categories')
def __str__(self):
return '{}'.format(self.category)
Here is my User model:
import uuid
# django
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import AbstractBaseUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
# models
from apps.API.models.BaseModel import BaseModel
from apps.API.manager import UserManager
class User(AbstractBaseUser, PermissionsMixin):
# Use UUID to make the merging of tables much easier if need be.
id = models.UUIDField(primary_key=True,
editable=False,
default=uuid.uuid4,
verbose_name='public identifier',)
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'),
max_length=255,
blank=True)
last_name = models.CharField(_('last name'),
max_length=255,
blank=True)
created_date = models.DateTimeField(_('created date'),
auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
is_superuser = models.BooleanField(_('superuser'), default=False)
is_staff = models.BooleanField(_('staff'), default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
app_label = "API"
verbose_name = _('User')
verbose_name_plural = _('Users')
def __str__(self):
return '{}'.format(self.email)
I popped into the Django shell and had the same issues with mixer so this is not an issue with pytest.
Traceback (most recent call last):
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-
packages/django/db/models/fields/__init__.py", line 2325, in to_python
return uuid.UUID(value)
File "/usr/lib/python3.6/uuid.py", line 137, in __init__
hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'User' object has no attribute 'replace'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 568, in blend
return type_mixer.blend(**values)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 116, in blend
for name, value in defaults.items()
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 116, in <genexpr>
for name, value in defaults.items()
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/mix_types.py", line 222, in gen_value
return type_mixer.gen_field(field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 270, in gen_field
return super(TypeMixer, self).gen_field(field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 193, in gen_field
return self.gen_value(field.name, field, unique=unique)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 261, in gen_value
return self.get_value(field_name, value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 218, in get_value
return self._get_value(name, value, field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 233, in _get_value
value = field.scheme.to_python(value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/django/db/models/fields/related.py", line 874, in to_python
return self.target_field.to_python(value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py", line 2330, in to_python
params={'value': value},
django.core.exceptions.ValidationError: ["'[email protected]' is not a valid UUID."]
It seems that this is a versioning issue as Mixer does not support Django 2.1 yet.
This PR supposedly will fix the issue. Not sure when or if it will be merged, as there have been no updates to the Mixer repo for 5 months.
Mixer PR for Django 2.1 Support on Github