-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The microsecond provider uses more precise clock resolution to create randomness values that should monotonically increase on timestamp (millisecond) collision. When creating a new ULID, we extract the number of microseconds from our timestamp and use them as the first two bytes of the randomness value. Caveats: When using `ulid.from_timestamp`, we fallback to the default implementation of 10 bytes of pure randomness. The reason for this, is that the timestamp that is being passed in by the caller is in milliseconds. Since we don't have a more accurate timestamp to "steal" bytes from, there's not much we can do. We cannot make a new epoch timestamp as the microsecond remainder is only accurate when working within the same millisecond.
- Loading branch information
Showing
18 changed files
with
305 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
""" | ||
test_api_microsecond | ||
~~~~~~~~~~~~~~~~~~~~ | ||
Tests for the :mod:`~ulid.api.microsecond` module. | ||
""" | ||
from ulid.api import microsecond | ||
from ulid.api.api import ALL | ||
|
||
|
||
def test_module_has_dunder_all(): | ||
""" | ||
Assert that :mod:`~ulid.api.microsecond` exposes the :attr:`~ulid.api.__all__` attribute as a list. | ||
""" | ||
assert hasattr(microsecond, '__all__') | ||
assert isinstance(microsecond.__all__, list) | ||
|
||
|
||
def test_module_exposes_expected_interface(): | ||
""" | ||
Assert that :attr:`~ulid.api.microsecond.__all__` exposes expected interface. | ||
""" | ||
assert microsecond.__all__ == ALL | ||
|
||
|
||
def test_module_api_uses_correct_provider(): | ||
""" | ||
Assert that the API instance uses the correct provider type. | ||
""" | ||
assert isinstance(microsecond.API.provider, type(microsecond.providers.MICROSECOND)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
""" | ||
test_providers_microsecond | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
Tests for the :mod:`~ulid.providers.microsecond` module. | ||
""" | ||
import pytest | ||
|
||
from ulid.providers import base, default, microsecond, time | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def provider(): | ||
""" | ||
Fixture that yields a microsecond provider instance. | ||
""" | ||
return microsecond.Provider(default.Provider()) | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def valid_epoch_milliseconds(): | ||
""" | ||
Fixture that yields a epoch value in milliseconds. | ||
""" | ||
return time.PROVIDER.milliseconds() | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def valid_epoch_microseconds(): | ||
""" | ||
Fixture that yields a epoch value in microseconds. | ||
""" | ||
return time.PROVIDER.microseconds() | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def mock_time_provider(mocker, valid_epoch_milliseconds, valid_epoch_microseconds): | ||
""" | ||
Fixture that yields a mock time provider. | ||
""" | ||
provider = mocker.Mock(spec=time.Provider) | ||
provider.milliseconds = mocker.Mock(return_value=valid_epoch_milliseconds) | ||
provider.microseconds = mocker.Mock(return_value=valid_epoch_microseconds) | ||
return provider | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def provider_time_mock(mocker, mock_time_provider): | ||
""" | ||
Fixture that yields a provider with time mock. | ||
""" | ||
mocker.patch.object(microsecond.time, 'milliseconds', side_effect=mock_time_provider.milliseconds) | ||
mocker.patch.object(microsecond.time, 'microseconds', side_effect=mock_time_provider.microseconds) | ||
return microsecond.Provider(default.Provider()) | ||
|
||
|
||
def test_provider_derives_from_base(): | ||
""" | ||
Assert that :class:`~ulid.providers.microsecond.Provider` derives from :class:`~ulid.providers.base.Provider`. | ||
""" | ||
assert issubclass(microsecond.Provider, base.Provider) | ||
|
||
|
||
def test_provider_new_returns_bytes_pair(provider): | ||
""" | ||
Assert that :meth:`~ulid.providers.microsecond.Provider.new` returns timestamp and randomness | ||
bytes of expected length as a two item tuple. | ||
""" | ||
value = provider.new() | ||
assert isinstance(value, tuple) | ||
assert len(value) == 2 | ||
assert len(value[0]) == 6 | ||
assert len(value[1]) == 10 | ||
|
||
|
||
def test_provider_new_returns_randomness_with_microseconds(provider_time_mock): | ||
""" | ||
Assert that :meth:`~ulid.providers.default.Provider.new` returns timestamp and randomness | ||
bytes that use microseconds as the first two bytes of randomness. | ||
""" | ||
epoch_us = time.microseconds() | ||
epoch_ms = epoch_us // 1000 | ||
microseconds = epoch_us % epoch_ms | ||
|
||
_, randomness = provider_time_mock.new() | ||
|
||
assert randomness[:2] == microseconds.to_bytes(2, byteorder='big') | ||
|
||
|
||
def test_provider_timestamp_returns_bytes(provider): | ||
""" | ||
Assert that :meth:`~ulid.providers.microsecond.Provider.timestamp` returns bytes of expected length. | ||
""" | ||
value = provider.timestamp() | ||
assert isinstance(value, bytes) | ||
assert len(value) == 6 | ||
|
||
|
||
def test_provider_timestamp_uses_time_epoch(provider): | ||
""" | ||
Assert that :meth:`~ulid.providers.microsecond.Provider.timestamp` returns the current time milliseconds | ||
since epoch in bytes. | ||
""" | ||
timestamp_bytes = provider.timestamp() | ||
timestamp_int = int.from_bytes(timestamp_bytes, byteorder='big') | ||
assert timestamp_int <= time.milliseconds() | ||
|
||
|
||
def test_provider_randomness_returns_bytes(provider): | ||
""" | ||
Assert that :meth:`~ulid.providers.microsecond.Provider.randomness` returns bytes of expected length. | ||
""" | ||
value = provider.randomness(provider.timestamp()) | ||
assert isinstance(value, bytes) | ||
assert len(value) == 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
""" | ||
ulid/api/microsecond | ||
~~~~~~~~~~~~~~~~~~~~ | ||
Contains the public API of the `ulid` package using the microsecond provider. | ||
""" | ||
from .. import consts, providers, ulid | ||
from . import api | ||
|
||
API = api.Api(providers.MICROSECOND) | ||
|
||
create = API.create | ||
from_bytes = API.from_bytes | ||
from_int = API.from_int | ||
from_randomness = API.from_randomness | ||
from_str = API.from_str | ||
from_timestamp = API.from_timestamp | ||
from_uuid = API.from_uuid | ||
new = API.new | ||
parse = API.parse | ||
|
||
MIN_TIMESTAMP = consts.MIN_TIMESTAMP | ||
MAX_TIMESTAMP = consts.MAX_TIMESTAMP | ||
MIN_RANDOMNESS = consts.MIN_RANDOMNESS | ||
MAX_RANDOMNESS = consts.MAX_RANDOMNESS | ||
MIN_ULID = consts.MIN_ULID | ||
MAX_ULID = consts.MAX_ULID | ||
|
||
Timestamp = ulid.Timestamp | ||
Randomness = ulid.Randomness | ||
ULID = ulid.ULID | ||
|
||
__all__ = api.ALL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.