Skip to main content

Say `Immobilus!` to freeze your tests

Project description

Download from PyPI Tests

A simple time freezing tool for python tests. It mocks:

  • datetime.date.today()

  • datetime.datetime.now()

  • datetime.datetime.utcnow()

  • datetime.datetime.fromtimestamp()

  • time.time()

  • time.gmtime()

  • time.localtime()

  • time.strftime()

  • time.mktime()

Usage

You must import immobilus before datetime or time, or any other module which imports them in turn, to allow it to intercept those modules.

>>> from immobilus import immobilus
>>> from datetime import datetime, timedelta

For example, if you use pytest, you could add import immobilus into your root conftest.py file.

Context manager

You can use immobilus as a context manager. When the context manager is active, time is frozen to the specified value. Outside of the context manager, the original standard library functions are used and time behaves normally.

>>> # It is unlikely that you are living in the past
>>> datetime.utcnow() == datetime(2017, 10, 20)
False
>>> # But with immobilus, you can pretend that you are
>>> with immobilus('2017-10-20'):
...     datetime.utcnow() == datetime(2017, 10, 20)
...
True
>>> # Once the context manager exits, immobilus deactivates.
>>> # We are back in the present.
>>> datetime.utcnow() == datetime(2017, 10, 20)
False

Specifying the freeze time

As shown above, you can use a string to describe the time to be frozen (e.g. '2017-10-20'). Any values understood by the dateutil.parser can be used.

You can also use a datetime.datetime object for the freeze time:

>>> naive_freeze_time = datetime(2017, 10, 20)
>>> with immobilus(naive_freeze_time):
...     print('now:    %s' % datetime.now())
...     print('utcnow: %s' % datetime.utcnow())
...
now:    2017-10-20 00:00:00
utcnow: 2017-10-20 00:00:00

immobilus will use the given datetime object to set the frozen UTC time and local time to the same value that you provide. If the datetime you provide for the freeze time is aware, then it is adjusted to UTC like so:

>>> import pytz
>>>
>>> # Freeze to 12:00 noon in Moscow (UTC+3)
>>> timezone = pytz.timezone('Europe/Moscow')
>>> aware_freeze_time = timezone.localize(datetime(2017, 10, 20, 12))
>>> with immobilus(aware_freeze_time):
...     # 9:00am local time which is same as UTC
...     print('now:    %s' % datetime.now())
...     print('utcnow: %s' % datetime.utcnow())
...
now:    2017-10-20 09:00:00
utcnow: 2017-10-20 09:00:00

If you want local time to differ from UTC, read on.

Freezing a local time that is not UTC time

To have a different timezone in effect when time is frozen, use the second argument to the immobilus context manager: tz_offset. This is the number of hours ahead of the frozen UTC time that the frozen local time should be.

>>> with immobilus('2017-10-20 09:00', tz_offset=3):
...     print('now:    %s' % datetime.now())
...     print('utcnow: %s' % datetime.utcnow())
...
now:    2017-10-20 12:00:00
utcnow: 2017-10-20 09:00:00

Of course, you can be behind UTC if you wish, by using a negative number:

>>> with immobilus('2017-10-20 09:00', tz_offset=-7):
...     print('now:    %s' % datetime.now())
...     print('utcnow: %s' % datetime.utcnow())
...
now:    2017-10-20 02:00:00
utcnow: 2017-10-20 09:00:00

You can move the frozen time point by calling the tick method:

>>> with immobilus('2019-08-21 12:00:00') as dt:
...     print(datetime.now())
...     dt.tick()
...     print(datetime.now())
...     dt.tick(timedelta(seconds=10))
...     print(datetime.now())
...
2019-08-21 12:00:00
2019-08-21 12:00:01
2019-08-21 12:00:11

Using as a decorator

As well as being a context manager, immobilus is also a decorator:

>>> @immobilus('2017-10-20')
... def test():
...     print(datetime.now())
...
>>> test()
2017-10-20 00:00:00

It works even with classes

>>> @immobilus('2017-10-20')
... class Decorated(object):
...     now = datetime.utcnow()
...
...     def first(self):
...         return datetime.utcnow()
...
...     def second(self):
...         return self.now
...
>>> d = Decorated()
>>> assert d.first().strftime('%Y-%m-%d %H:%M:%S') == '2017-10-20 00:00:00'
>>> assert d.second().strftime('%Y-%m-%d %H:%M:%S') != '2017-10-20 00:00:00'

and coroutines

>>> import asyncio
>>>
>>> @immobilus('2017-10-20')
... async def test():
...     return datetime.now()
...
>>> loop = asyncio.new_event_loop()
>>> result = loop.run_until_complete(test())
>>>
>>> assert result.strftime('%Y-%m-%d %H:%M:%S') == '2017-10-20 00:00:00'

Using directly

Or you can activate and deactivate immobilus manually.

>>> freeze_time = datetime(2017, 10, 20)
>>> spell = immobilus(freeze_time)
>>> datetime.utcnow() == freeze_time
False
>>> spell.start()
FakeDatetime(2017, 10, 20, 0, 0)
>>> datetime.utcnow() == freeze_time
True
>>> datetime.utcnow()
FakeDatetime(2017, 10, 20, 0, 0)
>>> spell.stop()
>>> datetime.utcnow() == freeze_time
False

This can be quite useful for those using the standard library unittest.TestCase e.g.

import unittest

class SomeTests(unittest.TestCase):
    def setUp(self):
        spell = immobilus('2017-10-20')
        spell.start()
        self.addCleanup(spell.stop)

Nesting

You can also nest context managers (or decorators, or direct invocations, or any combination) if you want to freeze different times.

>>> with immobilus('2017-10-20 12:00'):
...     print('outer now:    %s' % datetime.now())
...     print('outer utcnow: %s' % datetime.utcnow())
...     with immobilus('2017-10-21 12:00', tz_offset=5):
...         print('inner now:    %s' % datetime.now())
...         print('inner utcnow: %s' % datetime.utcnow())
...     print('outer now:    %s' % datetime.now())
...     print('outer utcnow: %s' % datetime.utcnow())
...
outer now:    2017-10-20 12:00:00
outer utcnow: 2017-10-20 12:00:00
inner now:    2017-10-21 17:00:00
inner utcnow: 2017-10-21 12:00:00
outer now:    2017-10-20 12:00:00
outer utcnow: 2017-10-20 12:00:00

Special thanks for contribution:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

immobilus-1.5.0.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

immobilus-1.5.0-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file immobilus-1.5.0.tar.gz.

File metadata

  • Download URL: immobilus-1.5.0.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.7.3

File hashes

Hashes for immobilus-1.5.0.tar.gz
Algorithm Hash digest
SHA256 c335a253eeacb45e176a2c0e4dce2fa17c1caa7fe227cbbf6b5b3f64b8b45cc3
MD5 047d135ec7f9a4215bb0295acd3a79cf
BLAKE2b-256 4b0e9b691fe0cdff75afaa7fb2abb4ea56708110709363a87664c25279758564

See more details on using hashes here.

File details

Details for the file immobilus-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: immobilus-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.7.3

File hashes

Hashes for immobilus-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9ada5085595ed9bad0a392e0d55e7a5769146c43a3861e54e186ba73afc9f2d8
MD5 886357826941498e5ac4fb8c06e4b207
BLAKE2b-256 3519c4cc61ca869b33c722f9a598ff94b948789eeb3ebd7adc6cd9d7536922a3

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page