Skip to content

Commit

Permalink
Add monotonic randomness support
Browse files Browse the repository at this point in the history
Doing this required a refactoring of how timestamp/randomness values
were generated. We've broken these into "provider" implementations
with the "default" provider being the implementation that exists today,
e.g. randomness values are random even on identical timestamp values.

A "monotonic" provider has been added which monotonically increments
the first randomness value on timestamp collision until an overflow.

Additionally, the API has been broken out into a subpackage so we can
stay agnostic to the provider and just plug it in. Work has been done
to maintain the existing package interface for backwards compatibility.
  • Loading branch information
ahawker committed Aug 18, 2020
1 parent bcbbe28 commit 3c64273
Show file tree
Hide file tree
Showing 22 changed files with 1,054 additions and 357 deletions.
93 changes: 93 additions & 0 deletions tests/test_api_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
test_api_api
~~~~~~~~~~~~
Tests for the :mod:`~ulid.api.api` module.
"""
import pytest

from ulid import providers
from ulid.api.api import ALL, Api


@pytest.fixture(scope='function')
def mock_provider(mocker):
"""
Fixture that yields a mock provider.
"""
provider = mocker.Mock(spec=providers.Provider)
provider.timestamp = mocker.Mock(side_effect=providers.DEFAULT.timestamp)
provider.randomness = mocker.Mock(side_effect=providers.DEFAULT.randomness)
return provider


@pytest.fixture(scope='function')
def mock_api(mock_provider):
"""
Fixture that yields a :class:`~ulid.api.api.Api` instance with a mock provider.
"""
return Api(mock_provider)


def test_all_defined_expected_methods():
"""
Assert that :attr:`~ulid.api.api.ALL` exposes expected interface.
"""
assert ALL == [
'new',
'parse',
'create',
'from_bytes',
'from_int',
'from_str',
'from_uuid',
'from_timestamp',
'from_randomness',
'MIN_TIMESTAMP',
'MAX_TIMESTAMP',
'MIN_RANDOMNESS',
'MAX_RANDOMNESS',
'MIN_ULID',
'MAX_ULID',
'Timestamp',
'Randomness',
'ULID'
]


def test_api_new_calls_provider_timestamp(mock_api):
"""
Assert :meth:`~ulid.api.api.Api.new` calls :meth:`~ulid.providers.base.Provider.timestamp` for a value.
"""
mock_api.new()

mock_api.provider.timestamp.assert_called_once()


def test_api_new_calls_provider_randomness(mock_api):
"""
Assert :meth:`~ulid.api.api.Api.new` calls :meth:`~ulid.providers.base.Provider.randomness` for a value.
"""
mock_api.new()

mock_api.provider.randomness.assert_called_once()


def test_api_from_timestamp_calls_provider_randomness(mock_api, valid_bytes_48):
"""
Assert :meth:`~ulid.api.api.Api.from_timestamp` calls :meth:`~ulid.providers.base.Provider.randomness` for a value.
"""
mock_api.from_timestamp(valid_bytes_48)

mock_api.provider.timestamp.assert_not_called()
mock_api.provider.randomness.assert_called_once()


def test_api_from_randomness_calls_provider_timestamp(mock_api, valid_bytes_80):
"""
Assert :meth:`~ulid.api.api.Api.from_randomness` calls :meth:`~ulid.providers.base.Provider.timestamp` for a value.
"""
mock_api.from_randomness(valid_bytes_80)

mock_api.provider.timestamp.assert_called_once()
mock_api.provider.randomness.assert_not_called()
23 changes: 23 additions & 0 deletions tests/test_api_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
test_api_default
~~~~~~~~~~~~~~~~
Tests for the :mod:`~ulid.api.default` module.
"""
from ulid.api import default
from ulid.api.api import ALL


def test_module_has_dunder_all():
"""
Assert that :mod:`~ulid.api.default` exposes the :attr:`~ulid.api.__all__` attribute as a list.
"""
assert hasattr(default, '__all__')
assert isinstance(default.__all__, list)


def test_module_exposes_expected_interface():
"""
Assert that :attr:`~ulid.api.default.__all__` exposes expected interface.
"""
assert default.__all__ == ALL
23 changes: 23 additions & 0 deletions tests/test_api_monotonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
test_api_monotonic
~~~~~~~~~~~~~~~~~~
Tests for the :mod:`~ulid.api.monotonic` module.
"""
from ulid.api import monotonic
from ulid.api.api import ALL


def test_module_has_dunder_all():
"""
Assert that :mod:`~ulid.api.monotonic` exposes the :attr:`~ulid.api.__all__` attribute as a list.
"""
assert hasattr(monotonic, '__all__')
assert isinstance(monotonic.__all__, list)


def test_module_exposes_expected_interface():
"""
Assert that :attr:`~ulid.api.monotonic.__all__` exposes expected interface.
"""
assert monotonic.__all__ == ALL
Loading

0 comments on commit 3c64273

Please sign in to comment.