Repository: coleifer/peewee

Issue:
Better support for UUIDField
As an extension from #780, databases which don't support UUID natively may encounter consistency problems. 

Postgres will correctly handle multiple input variants [\[1\]][1], such as
`a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11` and `a0eebc999c0b4ef8bb6d6bb9bd380a11`.

However SQLite, which now uses `TEXT` since #780, does not support multiple input types properly, and will end up with different variants being stored in the backend, rather than being normalised. Other databases, such as MySQL, are not supported at all as they don't have the necessary `field_overrides`.

The most viable fix I can see for this would be;

```py
class UUIDField(Field):
    ...
    def db_value(self, value):
        if isinstance(value, uuid.UUID):
            return value.hex
        try:
            return uuid.UUID(value).hex
        except:
            return value
```

The above would ensure that the UUID output is always consistent, whilst also allowing non confirming values to be used with `filter()`, e.g. `0f0f0f0f-` 

I'd also like to propose that we add a `field_override` for `MySQLDatabase`, much the same as `SqliteDatabase`.

If you're happy with the above solution, I'll throw up a PR, let me know


[1]: http://www.postgresql.org/docs/9.1/static/datatype-uuid.html

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index 6e0d4e46..b348417c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ playhouse/_speedups.so
 playhouse/tests/peewee_test.db
 .idea/
 MANIFEST
+peewee_test.db
diff --git a/peewee.py b/peewee.py
index 827d0896..c910682d 100644
--- a/peewee.py
+++ b/peewee.py
@@ -1038,7 +1038,12 @@ class UUIDField(Field):
     db_field = 'uuid'
 
     def db_value(self, value):
-        return None if value is None else str(value)
+        if isinstance(value, uuid.UUID):
+            return value.hex
+        try:
+            return uuid.UUID(value).hex
+        except:
+            return value
 
     def python_value(self, value):
         return None if value is None else uuid.UUID(value)
@@ -3751,6 +3756,7 @@ class MySQLDatabase(Database):
         'float': 'FLOAT',
         'primary_key': 'INTEGER AUTO_INCREMENT',
         'text': 'LONGTEXT',
+        'uuid': 'LONGTEXT'
     }
     for_update = True
     interpolation = '%s'


Test patch:
diff --git a/playhouse/tests/test_fields.py b/playhouse/tests/test_fields.py
index a9cfbcaf..1af7ee50 100644
--- a/playhouse/tests/test_fields.py
+++ b/playhouse/tests/test_fields.py
@@ -726,7 +726,6 @@ class TestServerDefaults(ModelTestCase):
         self.assertEqual(sd2_db.timestamp, datetime.datetime(2015, 1, 2, 3, 4))
 
 
-@skip_if(lambda: isinstance(test_db, MySQLDatabase))
 class TestUUIDField(ModelTestCase):
     requires = [
         TestingID,
@@ -745,6 +744,29 @@ class TestUUIDField(ModelTestCase):
         t2 = TestingID.get(TestingID.uniq == uuid_obj)
         self.assertEqual(t1, t2)
 
+    def test_uuid_casting(self):
+        uuid_obj = uuid.UUID('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')
+        uuid_str = uuid_obj.hex
+        uuid_str_short = uuid_str.replace("-", "")
+
+        t1 = TestingID.create(uniq=uuid_obj)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str_short)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+
+        t1 = TestingID.create(uniq=uuid_str)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str_short)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+
+        t1 = TestingID.create(uniq=uuid_str_short)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+        t1_db = TestingID.get(TestingID.uniq == uuid_str_short)
+        self.assertEqual(t1_db.uniq, uuid_obj)
+
     def test_uuid_foreign_keys(self):
         data_a = UUIDData.create(id=uuid.uuid4(), data='a')
         data_b = UUIDData.create(id=uuid.uuid4(), data='b')