Repository: HypothesisWorks/hypothesis

Issue:
Bug: ValueError: MySQL backend does not support timezone-aware datetimes when USE_TZ is False.
```python
class Dictionary(models.Model):
    timestamp = models.DateTimeField(default=0)
```

```python
----> 1 models(Dictionary).example()

/usr/local/lib/python3.6/site-packages/hypothesis/searchstrategy/strategies.py in example(self, random)
    112                     max_shrinks=0,
    113                     max_iterations=1000,
--> 114                     database=None
    115                 )
    116             )

/usr/local/lib/python3.6/site-packages/hypothesis/core.py in find(specifier, condition, settings, random, database_key)
    630         database_key=database_key,
    631     )
--> 632     runner.run()
    633     note_engine_for_statistics(runner)
    634     run_time = time.time() - start

/usr/local/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in run(self)
    189         with self.settings:
    190             try:
--> 191                 self._run()
    192             except RunIsComplete:
    193                 pass

/usr/local/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in _run(self)
    310                 self.last_data.status < Status.INTERESTING
    311             ):
--> 312                 self.new_buffer()
    313 
    314             mutator = self._new_mutator()

/usr/local/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in new_buffer(self)
     73             distribution(self.random, n)
     74         )
---> 75         self.test_function(self.last_data)
     76         self.last_data.freeze()
     77 

/usr/local/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in test_function(self, data)
     79         self.call_count += 1
     80         try:
---> 81             self._test_function(data)
     82             data.freeze()
     83         except StopTest as e:

/usr/local/lib/python3.6/site-packages/hypothesis/core.py in template_condition(data)
    596             try:
    597                 data.is_find = True
--> 598                 result = data.draw(search)
    599                 data.note(result)
    600                 success = condition(result)

/usr/local/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    103         self.start_example()
    104         try:
--> 105             return strategy.do_draw(self)
    106         finally:
    107             if not self.frozen:

/usr/local/lib/python3.6/site-packages/hypothesis/extra/django/models.py in do_draw(self, data)
    196         try:
    197             result, _ = self.model.objects.get_or_create(
--> 198                 **self.arg_strategy.do_draw(data)
    199             )
    200             return result

/usr/local/lib/python3.6/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
     83         def create_method(name, method):
     84             def manager_method(self, *args, **kwargs):
---> 85                 return getattr(self.get_queryset(), name)(*args, **kwargs)
     86             manager_method.__name__ = method.__name__
     87             manager_method.__doc__ = method.__doc__

/usr/local/lib/python3.6/site-packages/django/db/models/query.py in get_or_create(self, defaults, **kwargs)
    471         self._for_write = True
    472         try:
--> 473             return self.get(**lookup), False
    474         except self.model.DoesNotExist:
    475             return self._create_object_from_params(lookup, params)

/usr/local/lib/python3.6/site-packages/django/db/models/query.py in get(self, *args, **kwargs)
    377         if self.query.can_filter() and not self.query.distinct_fields:
    378             clone = clone.order_by()
--> 379         num = len(clone)
    380         if num == 1:
    381             return clone._result_cache[0]

/usr/local/lib/python3.6/site-packages/django/db/models/query.py in __len__(self)
    236 
    237     def __len__(self):
--> 238         self._fetch_all()
    239         return len(self._result_cache)
    240 

/usr/local/lib/python3.6/site-packages/django/db/models/query.py in _fetch_all(self)
   1085     def _fetch_all(self):
   1086         if self._result_cache is None:
-> 1087             self._result_cache = list(self.iterator())
   1088         if self._prefetch_related_lookups and not self._prefetch_done:
   1089             self._prefetch_related_objects()

/usr/local/lib/python3.6/site-packages/django/db/models/query.py in __iter__(self)
     52         # Execute the query. This will also fill compiler.select, klass_info,
     53         # and annotations.
---> 54         results = compiler.execute_sql()
     55         select, klass_info, annotation_col_map = (compiler.select, compiler.klass_info,
     56                                                   compiler.annotation_col_map)

/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py in execute_sql(self, result_type)
    822             result_type = NO_RESULTS
    823         try:
--> 824             sql, params = self.as_sql()
    825             if not sql:
    826                 raise EmptyResultSet

/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py in as_sql(self, with_limits, with_col_aliases, subquery)
    374             from_, f_params = self.get_from_clause()
    375 
--> 376             where, w_params = self.compile(self.where) if self.where is not None else ("", [])
    377             having, h_params = self.compile(self.having) if self.having is not None else ("", [])
    378             params = []

/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py in compile(self, node, select_format)
    351             sql, params = vendor_impl(self, self.connection)
    352         else:
--> 353             sql, params = node.as_sql(self, self.connection)
    354         if select_format and not self.subquery:
    355             return node.output_field.select_format(self, sql, params)

/usr/local/lib/python3.6/site-packages/django/db/models/sql/where.py in as_sql(self, compiler, connection)
     77         for child in self.children:
     78             try:
---> 79                 sql, params = compiler.compile(child)
     80             except EmptyResultSet:
     81                 empty_needed -= 1

/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py in compile(self, node, select_format)
    351             sql, params = vendor_impl(self, self.connection)
    352         else:
--> 353             sql, params = node.as_sql(self, self.connection)
    354         if select_format and not self.subquery:
    355             return node.output_field.select_format(self, sql, params)

/usr/local/lib/python3.6/site-packages/django/db/models/lookups.py in as_sql(self, compiler, connection)
    154     def as_sql(self, compiler, connection):
    155         lhs_sql, params = self.process_lhs(compiler, connection)
--> 156         rhs_sql, rhs_params = self.process_rhs(compiler, connection)
    157         params.extend(rhs_params)
    158         rhs_sql = self.get_rhs_op(connection, rhs_sql)

/usr/local/lib/python3.6/site-packages/django/db/models/lookups.py in process_rhs(self, compiler, connection)
     90             return '(' + sql + ')', params
     91         else:
---> 92             return self.get_db_prep_lookup(value, connection)
     93 
     94     def rhs_is_direct_value(self):

/usr/local/lib/python3.6/site-packages/django/db/models/lookups.py in get_db_prep_lookup(self, value, connection)
    180             [get_db_prep_value(v, connection, prepared=True) for v in value]
    181             if self.get_db_prep_lookup_value_is_iterable else
--> 182             [get_db_prep_value(value, connection, prepared=True)]
    183         )
    184 

/usr/local/lib/python3.6/site-packages/django/db/models/fields/__init__.py in get_db_prep_value(self, value, connection, prepared)
   1437         if not prepared:
   1438             value = self.get_prep_value(value)
-> 1439         return connection.ops.adapt_datetimefield_value(value)
   1440 
   1441     def value_to_string(self, obj):

/usr/local/lib/python3.6/site-packages/django/db/backends/mysql/operations.py in adapt_datetimefield_value(self, value)
    147                 value = timezone.make_naive(value, self.connection.timezone)
    148             else:
--> 149                 raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
    150 
    151         if not self.connection.features.supports_microsecond_precision:

ValueError: MySQL backend does not support timezone-aware datetimes when USE_TZ is False.
```
```
$ python --version
Python 3.6.0
$ pip freeze | grep hypothesis
hypothesis==3.6.1
```


Discussion:
Hmm. That's interesting.

It looks like the real bug here is that hypothesis is not (by default) respecting the Django USE_TZ setting. Good catch, thanks.
FWIW you can work around this until it's fixed by explicitly passing in a `datetimes(timezone=None)` for the relevant fields.

Resolving patch:
diff --git a/src/hypothesis/extra/django/models.py b/src/hypothesis/extra/django/models.py
--- a/src/hypothesis/extra/django/models.py
+++ b/src/hypothesis/extra/django/models.py
@@ -22,6 +22,7 @@
 
 import django.db.models as dm
 from django.db import IntegrityError
+from django.conf import settings as django_settings
 from django.core.exceptions import ValidationError
 
 import hypothesis.strategies as st
@@ -47,6 +48,12 @@ def referenced_models(model, seen=None):
     return seen
 
 
+def get_datetime_strat():
+    if getattr(django_settings, 'USE_TZ', False):
+        return datetimes(allow_naive=False)
+    return datetimes(timezones=[])
+
+
 __default_field_mappings = None
 
 
@@ -63,7 +70,7 @@ def field_mappings():
             dm.PositiveSmallIntegerField: st.integers(0, 32767),
             dm.BinaryField: st.binary(),
             dm.BooleanField: st.booleans(),
-            dm.DateTimeField: datetimes(allow_naive=False),
+            dm.DateTimeField: get_datetime_strat(),
             dm.FloatField: st.floats(),
             dm.NullBooleanField: st.one_of(st.none(), st.booleans()),
         }
diff --git a/src/hypothesis/version.py b/src/hypothesis/version.py
--- a/src/hypothesis/version.py
+++ b/src/hypothesis/version.py
@@ -17,5 +17,5 @@
 
 from __future__ import division, print_function, absolute_import
 
-__version_info__ = (3, 9, 0)
+__version_info__ = (3, 9, 1)
 __version__ = '.'.join(map(str, __version_info__))


Test patch:
diff --git a/tests/django/toys/settings.py b/tests/django/toys/settings.py
--- a/tests/django/toys/settings.py
+++ b/tests/django/toys/settings.py
@@ -96,7 +96,7 @@
 
 USE_L10N = True
 
-USE_TZ = True
+USE_TZ = os.environ.get('HYPOTHESIS_DJANGO_USETZ', 'TRUE') == 'TRUE'
 
 
 # Static files (CSS, JavaScript, Images)
diff --git a/tests/django/toystore/test_given_models.py b/tests/django/toystore/test_given_models.py
--- a/tests/django/toystore/test_given_models.py
+++ b/tests/django/toystore/test_given_models.py
@@ -17,6 +17,8 @@
 
 from __future__ import division, print_function, absolute_import
 
+from django.conf import settings as django_settings
+
 from hypothesis import given, assume
 from hypothesis.errors import InvalidArgument
 from hypothesis.strategies import just, lists
@@ -56,6 +58,13 @@ def test_is_customer(self, customer):
         self.assertIsNotNone(customer.pk)
         self.assertIsNotNone(customer.email)
 
+    @given(models(Customer))
+    def test_tz_presence(self, customer):
+        if django_settings.USE_TZ:
+            self.assertIsNotNone(customer.birthday.tzinfo)
+        else:
+            self.assertIsNone(customer.birthday.tzinfo)
+
     @given(models(CouldBeCharming))
     def test_is_not_charming(self, not_charming):
         self.assertIsInstance(not_charming, CouldBeCharming)