Skip to main content

Warp time

Project description

https://github.com/adamchainz/time-machine/workflows/CI/badge.svg?branch=master https://coveralls.io/repos/adamchainz/time-machine/badge.svg https://img.shields.io/pypi/v/time-machine.svg https://img.shields.io/badge/code%20style-black-000000.svg

Travel through time in your tests.

A quick example:

import datetime as dt
import time_machine

@time_machine.travel("1955-11-05 01:22")
def test_delorean():
    assert dt.date.today().isoformat() == "1955-11-05"

Installation

Use pip:

python -m pip install time-machine

Python 3.6 to 3.8 supported (CPython only).

Usage

travel(destination, *, tick=True, tz_offset=None)

travel() is a class that allows time travel, to the datetime specified by destination. It does so by mocking all functions from Python’s standard library that return the current date or datetime. It can be used independently, as a function decorator, or as a context manager.

destination specifies the datetime to move to. It may be:

  • A datetime.datetime. If it is naive, it will be assumed to have the UTC timezone.

  • A datetime.date. This will be converted to a UTC datetime with the time 00:00:00.

  • A float or int specifying a Unix timestamp

  • A string, which will be parsed with dateutil.parse and converted to a timestamp.

Additionally, you can provide some more complex types:

  • A generator, in which case next() will be called on it, with the result treated as above.

  • A callable, in which case it will be called with no parameters, with the result treated as above.

tick defines whether time continues to “tick” after travelling, or is frozen. If True, the default, successive calls to mocked functions return values increasing by the elapsed real time since the first call. So after starting travel to 0.0 (the UNIX epoch), the first call to any datetime function will return its representation of 1970-01-01 00:00:00.000000 exactly. The following calls “tick,” so if a call was made exactly half a second later, it would return 1970-01-01 00:00:00.500000.

tz_offset allows you to offset the given destination. It may be a timedelta or a number of seconds, which will be added to destination. It may be negative.

Mocked Functions

All datetime functions in the standard library are mocked to move to the destination current datetime:

  • datetime.datetime.now()

  • datetime.datetime.utcnow()

  • time.gmtime()

  • time.localtime()

  • time.clock_gettime() (only for CLOCK_REALTIME)

  • time.clock_gettime_ns() (only for CLOCK_REALTIME)

  • time.strftime()

  • time.time()

  • time.time_ns()

This mocking is done at the C layer, replacing the function pointers for these built-ins. Therefore, it automatically affects everywhere those functions have been imported, unlike use of unittest.mock.patch().

Usage with start() / stop()

To use independently, create and instance, use start() to move to the destination time, and stop() to move back. For example:

import datetime as dt
import time_machine

traveller = time_machine.travel(dt.datetime(1955, 11, 5))
traveller.start()
# It's the past!
assert dt.date.today() == dt.date(1955, 11, 5)
traveller.stop()
# We've gone back to the future!
assert dt.date.today() > dt.date(2020, 4, 29)

travel() instances are nestable, but you’ll need to be careful when manually managing to call their stop() methods in the correct order, even when exceptions occur. It’s recommended to use the decorator or context manager forms instead, to take advantage of Python features to do this.

Function Decorator

When used as a function decorator, time is mocked during the wrapped function’s duration:

import time
import time_machine

@time_machine.travel("1970-01-01 00:00 +0000")
def test_in_the_deep_past():
    assert 0.0 < time.time() < 1.0

This also works for asynchronous functions (coroutines):

import time
import time_machine

@time_machine.travel("1970-01-01 00:00 +0000")
async def test_in_the_deep_past():
    assert 0.0 < time.time() < 1.0

Context Manager

When used as a context manager, time is mocked during the with block:

def test_in_the_deep_past():
    with time_machine.travel(0.0):
        assert 0.0 < time.time() < 1.0

Class Decorator

Only unittest.TestCase subclasses are supported. When applied as a class decorator to such classes, time is mocked from the start of setUpClass() to the end of tearDownClass():

import time
import time_machine
import unittest

@time_machine.travel(0.0)
class DeepPastTests(TestCase):
    def test_in_the_deep_past(self):
        assert 0.0 < time.time() < 1.0

Note this is different to unittest.mock.patch()'s behaviour, which is to mock only during the test methods.

Caveats

Time is global state. Any concurrent threads or async functions are also be affected. Some aren’t ready for time to move so rapidly or backwards, and may crash or produce unexpected results.

Also beware that other processes are not affected. For example, if you use datetime functions in a client/server database they will still return the real time.

Comparison

There are some prior libraries that try to achieve the same thing. They have their own strengths and weaknesses. Here’s a quick comparison.

unittest.mock

The standard library’s unittest.mock can be used to target imports of datetime and time to change the returned value for current time. Unfortunately, this is fragile as it only affects the import location the mock targets. Therefore, if you have several modules in a call tree requesting the date/time, you need several mocks. This is a general problem with unittest.mock - see Why Your Mock Doesn’t Work.

It’s also impossible to mock certain references, such as function default arguments:

def update_books(_now=time.time):  # set as default argument so faster lookup
    for book in books:
        ...

Although this is rare, it’s often used to optimize repeat loops.

freezegun

Steve Pulec’s freezegun library is a popular solution. It provides a clear API which was much of the inspiration for time-machine.

The main drawback is its slow implementation. It essentially does a find-and-replace mock of all the places that the datetime and time modules have been imported. This gets around the problems with using unittest.mock, but it means the time it takes to do the mocking is proportional to the number of loaded modules. In large projects, this can take several seconds, an impractical overhead for an individual test.

Also, it can’t affect C extensions that call the standard library functions, including Cython-ized Python code.

Additionally, it has the same problem that it can’t mock function default arguments and some other references.

python-libfaketime

Simon Weber’s python-libfaketime wraps the LD_PRELOAD library libfaketime. libfaketime replaces all the C-level system calls for the current time with its own wrappers. It’s therefore a “perfect” mock for the current process, affecting every single point the current time might be fetched, and performs much faster than freezegun.

Unfortunately it comes with the limitations of LD_PRELOAD (explanation). First, this is only available on Unix platforms, which prevents it from working on Windows. Seccond, you either use its reexec_if_needed() function, which restarts (re-execs) your tests’ process once while loading, or manually manage the LD_PRELOAD environment variable (everywhere you run your tests). Re-execing breaks profilers, use of python -m pdb and similar, and other things that might wrap your test process. Manually managing the environment variable is a bit of overhead for each environment you want to run your tests in.

time-machine

time-machine is intended to combine the advantages of freezegun and libfaketime. It works without LD_PRELOAD but still mocks the standard library functions everywhere they may be referenced. Its weak point is that other libraries using date/time system calls won’t be mocked. Thankfully this is rare. It’s also possible such python libraries can be added to the set mocked by time-machine.

One drawback is that it only works with CPython, so can’t be used with other Python interpreters like PyPy. However it may possible to extend it to support other interpreters through different mocking mechanisms.

Migrating from libfaketime or freezegun

freezegun has a useful API, and python-libfaketime copies some of it, with a different function name. time-machine also copies some of freezegun’s API, in travel()'s destination, tick, and tz_offset arguments. There is one difference - time-machine’s tick argument defaults to True, because code tends to make the (reasonable) assumption that time progresses between function calls, and should normally be tested as such.

Some arguments aren’t supported like auto_tick_seconds, or the move_to() and tick() methods. These may be added in a future release.

If you are only fairly simple function calls, you should be able to migrate by replacing calls to freezegun.freeze_time() and libfaketime.fake_time() with time_machine.travel().

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

time-machine-1.0.1.tar.gz (60.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

time_machine-1.0.1-cp38-cp38-manylinux2010_x86_64.whl (31.6 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.12+ x86-64

time_machine-1.0.1-cp38-cp38-manylinux1_x86_64.whl (31.6 kB view details)

Uploaded CPython 3.8

time_machine-1.0.1-cp38-cp38-manylinux1_i686.whl (23.9 kB view details)

Uploaded CPython 3.8

time_machine-1.0.1-cp38-cp38-macosx_10_14_x86_64.whl (11.2 kB view details)

Uploaded CPython 3.8macOS 10.14+ x86-64

time_machine-1.0.1-cp37-cp37m-manylinux2010_x86_64.whl (29.7 kB view details)

Uploaded CPython 3.7mmanylinux: glibc 2.12+ x86-64

time_machine-1.0.1-cp37-cp37m-manylinux1_x86_64.whl (29.7 kB view details)

Uploaded CPython 3.7m

time_machine-1.0.1-cp37-cp37m-manylinux1_i686.whl (22.4 kB view details)

Uploaded CPython 3.7m

time_machine-1.0.1-cp36-cp36m-manylinux2010_x86_64.whl (27.5 kB view details)

Uploaded CPython 3.6mmanylinux: glibc 2.12+ x86-64

time_machine-1.0.1-cp36-cp36m-manylinux1_x86_64.whl (27.5 kB view details)

Uploaded CPython 3.6m

time_machine-1.0.1-cp36-cp36m-manylinux1_i686.whl (21.2 kB view details)

Uploaded CPython 3.6m

File details

Details for the file time-machine-1.0.1.tar.gz.

File metadata

  • Download URL: time-machine-1.0.1.tar.gz
  • Upload date:
  • Size: 60.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time-machine-1.0.1.tar.gz
Algorithm Hash digest
SHA256 f59f31804f6c60bb7e37b798090edc18a158487277fd31828c478028594fffe8
MD5 0a7d50573d25ff9b8b93d26cebf6eeb5
BLAKE2b-256 76893a7f9ec6e9297af401655ea5f1418137518e1af4acca8ead11448932de96

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp38-cp38-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp38-cp38-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 31.6 kB
  • Tags: CPython 3.8, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp38-cp38-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 b77ea5540c1bd6ae88de87c068e3e4dd211bc96bf9b503a1fd437d4c84e3e9d1
MD5 700efc072728bc4e80448ff485ddb70a
BLAKE2b-256 2a0775aabbcb135ee9a170d060a63797241002f2ff476e8bc4cf88b0e3bfbc6c

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp38-cp38-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp38-cp38-manylinux1_x86_64.whl
  • Upload date:
  • Size: 31.6 kB
  • Tags: CPython 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp38-cp38-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 75297dca36e8adb4ca5589cb2f267cd044fabc72ec9ee604d39c71c1f473047b
MD5 4d17728d5841e588d605937ce1517261
BLAKE2b-256 f89e8a9001507e0abaed36bd619b942c1d85b83428168155c189d42ccb1559ac

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp38-cp38-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp38-cp38-manylinux1_i686.whl
  • Upload date:
  • Size: 23.9 kB
  • Tags: CPython 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp38-cp38-manylinux1_i686.whl
Algorithm Hash digest
SHA256 c0a3a79e0e32c34171b4dd961f4ee3e1af159718f85f3d8e65cd616f3b388fce
MD5 cd93bbf9427d6fc0718b1ea1846e938c
BLAKE2b-256 1854b6af7162d9165be5b93e6061b5fea800689c860a0cb97e477faa7d1fa583

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp38-cp38-macosx_10_14_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp38-cp38-macosx_10_14_x86_64.whl
  • Upload date:
  • Size: 11.2 kB
  • Tags: CPython 3.8, macOS 10.14+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp38-cp38-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 2fc4b02a9dbf668919dff39ea3a9badcd42727aa78b233f41626f2f0486ab321
MD5 24fc48a33e42faf1dc24fd5c0685c32b
BLAKE2b-256 f5e78f39aa1df0487f326ded92a399d892f425e55dbbfe8b07ba4bf834f00f2a

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp37-cp37m-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp37-cp37m-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 29.7 kB
  • Tags: CPython 3.7m, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp37-cp37m-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 608a8c61dcc44a2723606696069c191cf80449f0a73f9161e278d90414792e29
MD5 cf027baec48c1256e5b5da14f617391a
BLAKE2b-256 952b4093591d5dca2c069c350fcc825d30b8df2e80a0684448d587295c1367de

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp37-cp37m-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp37-cp37m-manylinux1_x86_64.whl
  • Upload date:
  • Size: 29.7 kB
  • Tags: CPython 3.7m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp37-cp37m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 d014873dbc961867d0c6f326840b1f9f093d48d01f8f95fe21e838b457f88aff
MD5 573a4fe87dc686c0bc53aa95530a549a
BLAKE2b-256 d944fa7eff7686a0e7f3ad3f4caec2f7d1c818fa1da92a48fbe97d37456dd187

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp37-cp37m-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp37-cp37m-manylinux1_i686.whl
  • Upload date:
  • Size: 22.4 kB
  • Tags: CPython 3.7m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp37-cp37m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 1ef9cdf28029182532e090e1ccbd767e7b30ae8d5a3c4c22e23e6159e3d27ad7
MD5 3a92f7a387fdd3b0400db27ffa4a9b8f
BLAKE2b-256 696b957436531657e8a30b72ed928f40adb1c980614653a62040c74a41239772

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp36-cp36m-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp36-cp36m-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 27.5 kB
  • Tags: CPython 3.6m, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp36-cp36m-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 4b63b5aeaf6dd63e8e0dfcae384e427553ebeb9ca0ef79b4b9fc26044751a0c6
MD5 f8dce5a57e0c9e367ff621c86032ad10
BLAKE2b-256 20cdcc234c27f688e784ecfd42d529490aef6cefbbcd9ae3693582b50091fb4e

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp36-cp36m-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp36-cp36m-manylinux1_x86_64.whl
  • Upload date:
  • Size: 27.5 kB
  • Tags: CPython 3.6m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp36-cp36m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 03d3c13e7c7f08b2babae968b5ec79103b636c081be2804b1550afe01e740613
MD5 59d9e19548ffa9d2d5a6d857e50098f3
BLAKE2b-256 60c843ce765454fdd3b1e042a074dbcaa3f6ffc5875e88298a6812ccc07bb1ce

See more details on using hashes here.

File details

Details for the file time_machine-1.0.1-cp36-cp36m-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.0.1-cp36-cp36m-manylinux1_i686.whl
  • Upload date:
  • Size: 21.2 kB
  • Tags: CPython 3.6m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.8.2

File hashes

Hashes for time_machine-1.0.1-cp36-cp36m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 9688d2c860e06f1f2beb3aba3279597d2981ef35ace61bf830d33d304deda137
MD5 bdee1b373d24dd766f7d5d9a7d28467e
BLAKE2b-256 9c7f2f40c3e032797bf0dd35f0211e7b992b1349b010dcff57e82b5a5b451a52

See more details on using hashes here.

Supported by

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