#29852: Infinite migrations when using SimpleLazyObject in field arguments
------------------------------+------------------------------------
Reporter: Javier Buzzi | Owner: nobody
Type: Bug | Status: new
Component: Migrations | Version: master
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
------------------------------+------------------------------------
Comment (by Simon Charette):
The more I think of it the more I feel like the infinite migrations here
are expected and this is misuse of `SimpleLazyObject`.
`SimpleLazyObject(foo)` is really just a way of saying ''execute `foo` on
first access but not now'' as demonstrated by the following snippet
{{{#!python
In [1]: from datetime import datetime
In [2]: from django.utils.functional import SimpleLazyObject
In [3]: lazy_now = SimpleLazyObject(datetime.now)
In [4]: lazy_now.strftime("%Y-%m-%d %H:%M:%S")
Out[4]: '2018-10-16 15:26:54'
In [5]: import time; time.sleep(1)
In [6]: lazy_now.strftime("%Y-%m-%d %H:%M:%S")
Out[6]: '2018-10-16 15:26:54'
}}}
In this sense `MinValueValidator(SimpleLazyObject(datetime.now))` is
really just a slightly deferred `datetime.now()` as the first time the
wrapped function is evaluated the returned value is persisted. In other
words `MinValueValidator(SimpleLazyObject(datetime.now))` is really
equivalent to `MinValueValidator(datetime.now())` and is the same class of
problems the `DateTimeField(default=datetime.now())` check was dealing
with.
Now what I think you were trying to do here is provide a validator that
makes sure it's not possible to provide a past datetime value and this was
not working appropriately as the validator was actually performing a check
against the first time the `SimpleLazyObject` wrapped function was
resolved. For this purpose you should be using `django.utils.lazy`
instead.
{{{#!python
In [1]: from datetime import datetime, timedelta
In [2]: from django.utils.functional import lazy
In [3]: lazy_now = lazy(datetime.now, datetime)()
In [4]: from django.core.validators import MinValueValidator
In [5]: validator = MinValueValidator(lazy_now)
In [6]: lazy_now
Out[6]: datetime.datetime(2018, 10, 16, 14, 38, 59, 125760)
In [7]: type(lazy_now)
Out[7]: django.utils.functional.__proxy__
In [8]: validator(datetime.now())
ValidationError: [u'Ensure this value is greater than or equal to
2018-10-16 14:39:24.060516.']
In [9]: validator(datetime.now() + timedelta(seconds=1)) # account for
the function call duration
}}}
The only issue here is that `Promise` serialization assumes `lazy`
[https://github.com/django/django/blob/1c0bf95ff6f0dd32dd0424f0becbdfcf344d37be/django/db/migrations/serializer.py#L275-L276
is always dealing with strings] because the main use of them internally is
for translations through the `gettext` function #21008.
In summary, this is a misuse of `SimpleLazyObject` and `lazy` should have
been used instead but `Promise` serialization currently assumes it's only
used for string values and it should be adjusted to deal with other types
as well. What I suggest doing is inspecting wrapped types of the promise
object and only perform a `str` on the value if the types are `(str,)`.
Else the value should be deconstructed as a `django.utils.functional.lazy`
import and a `lazy(...)()` reconstruction.
--
Ticket URL: <https://code.djangoproject.com/ticket/29852#comment:6>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/069.018cd5879a87fc613373ee5aa38e3a5d%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.