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 25, 2020
1 parent 888b087 commit 7ce680a
Show file tree
Hide file tree
Showing 23 changed files with 1,070 additions and 358 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=filter-builtin-not-iterating,raw_input-builtin,cmp-builtin,buffer-builtin,unpacking-in-except,xrange-builtin,old-ne-operator,backtick,coerce-method,standarderror-builtin,long-suffix,reload-builtin,file-builtin,round-builtin,intern-builtin,raising-string,zip-builtin-not-iterating,no-absolute-import,unichr-builtin,parameter-unpacking,input-builtin,print-statement,indexing-exception,delslice-method,setslice-method,nonzero-method,long-builtin,hex-method,basestring-builtin,next-method-called,import-star-module-level,coerce-builtin,old-division,oct-method,map-builtin-not-iterating,range-builtin-not-iterating,reduce-builtin,apply-builtin,dict-view-method,useless-suppression,old-octal-literal,execfile-builtin,dict-iter-method,unicode-builtin,using-cmp-argument,metaclass-assignment,cmp-method,getslice-method,old-raise-syntax,suppressed-message
disable=filter-builtin-not-iterating,raw_input-builtin,cmp-builtin,buffer-builtin,unpacking-in-except,xrange-builtin,old-ne-operator,backtick,coerce-method,standarderror-builtin,long-suffix,reload-builtin,file-builtin,round-builtin,intern-builtin,raising-string,zip-builtin-not-iterating,no-absolute-import,unichr-builtin,parameter-unpacking,input-builtin,print-statement,indexing-exception,delslice-method,setslice-method,nonzero-method,long-builtin,hex-method,basestring-builtin,next-method-called,import-star-module-level,coerce-builtin,old-division,oct-method,map-builtin-not-iterating,range-builtin-not-iterating,reduce-builtin,apply-builtin,dict-view-method,useless-suppression,old-octal-literal,execfile-builtin,dict-iter-method,unicode-builtin,using-cmp-argument,metaclass-assignment,cmp-method,getslice-method,old-raise-syntax,suppressed-message,duplicate-code


[REPORTS]
Expand Down
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_with()


def test_api_new_calls_provider_randomness(mocker, 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_with(mocker.ANY)


def test_api_from_timestamp_calls_provider_randomness(mocker, 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_with(mocker.ANY)


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_with()
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 7ce680a

Please sign in to comment.