python / cpython

The Python programming language
https://www.python.org
Other
62.37k stars 29.96k forks source link

Problems with mock related to `datetime.utcnow()` #94497

Open jefer94 opened 2 years ago

jefer94 commented 2 years ago

Feature or enhancement

Is necessary fix the function isinstance or MagicMock can work together, of instead can mock a method of inmutable class a least when you have in a development environment

# django.utils.timezone

def now():
    return datetime.utcnow().replace(tzinfo=utc)
# django.contrib.auth.models

class User:
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
# django.db.models.fields

class DateTimeField:
    def to_python(self, value):
        if isinstance(value, datetime.datetime):
            ...
# test case 1

# TypeError: cannot set 'utcnow' attribute of immutable type 'datetime.datetime'
@patch('datetime.datetime.utcnow', MagicMock())  
def test_():
    ...
# test case 2

class DatetimeMock(datetime.datetime):
    @classmethod
    def utcnow(cls):
        ...

# E       - [{'date_joined': DatetimeMock(2022, 7, 1, 0, 0, tzinfo=<UTC>),
# E       ?                  ^       ^^^^             ^  ^
# E       
# E       + [{'date_joined': datetime.datetime(2022, 7, 1, 16, 47, 39, 294114, tzinfo=<UTC>),
# E       ?                  ^       ^^^^^^^^^             ^^  ^^ ++++++++++++
@patch('datetime.datetime.utcnow', DatetimeMock)  
def test_():
    ...
# test case 3

mock = MagicMock(wraps=datetime.datetime)
mock.utcnow.return_value = NOW

# TypeError: Mixer (authenticate.UserInvite): isinstance() arg 2 must be a type, a tuple of types, or a union
@patch('datetime.datetime', mock)
def test_():
    ...

Pitch

I just need can mock it, I think the solution of pass this issue to the team of @django is a bad idea because this problem should be replicated by other teams and the root the problem is in python, not in the other teams

merwok commented 2 years ago

I don’t know that this can be fixed; Mock is not designed to trick isinstance, django models want real datetimes, so you can’t use one for the other. The usual way here is to use something like freezegun to control the time values, not mock.

jefer94 commented 2 years ago

Yes but for a reason I don't know I can't install freezegun, in a normal case I use @patch('django.utils.timezone.now', MagicMock(return_value=UTC_NOW)) but in this case the date_joined field have the reference of original timezone.now, this is a mess, my solution was

        users = [
            x for x in self.bc.database.list_of('auth.User')
            # datetime_in_range is an assert
            if self.bc.check.datetime_in_range(start, end, x['date_joined']) or x.pop('date_joined')
        ]

        self.assertEqual(users, [{
            'email': model.user_invite.email,
            'first_name': '',
            'id': 1,
            'is_active': True,
            'is_staff': False,
            'is_superuser': False,
            'last_login': None,
            'last_name': '',
            'password': '',
            'username': model.user_invite.email,
        }])

I have a mini framework of mixins for tests

jefer94 commented 2 years ago

@spulec of freezegun and @adamchainz of time-machine, is the same issue in the two libs, remove the Pipfile.lock don't resolve it image

Also, @pypa of pipenv the dependencies could not be resolved, which? why? what's the dependencies in conflicts?

merwok commented 2 years ago

Please keep issues distinct and don’t mix 4 projects here!

jefer94 commented 2 years ago

That is the context 🤷‍♂️

tirkarthi commented 2 years ago

https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock

spec: This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError. If spec is an object (rather than a list of strings) then class returns the class of the spec object. This allows mocks to pass isinstance() tests.

Does passing a spec as datetime help in this case?

jefer94 commented 2 years ago

That apparently works ( the solution of @tirkarthi ) but i just want to remind that the docs are unreadable for junior developers, the docs should be the way all developers can intuitively find and read everything related in this case to unittest without finding solutions elsewhere like stackoverflow , I will make a proposal for you, in favor of the style of docs like Django Rest Framework, Express.JS and Pug, these are more oriented to explain the cases with examples, they are self-descriptive, this style is easier to read for junior developers, too, significantly improves the UX of the page, seeing a lot of text in one part usually causes the developer to close the page, see that the example appears too far from the spec description and this does not contain an intuitive sign indicating that here there is the part that I have to read to solve my problem, which will probably end with closing the page before finding the solution, and in part you are right, for all the programmers, time is money, and they need to be sure of the result of reading all the docs will end up solving their problem, if they see that the docs are very complex, they will never want to read them

image

merwok commented 2 years ago

There is an existing project to apply the Diataxis system to Python docs, which would better split documents between guides, tutorials and references.