"""Utility to generate a random UUID"""
import random as _py_random
import uuid as _uuid
from hashlib import sha512
from typing import Any, Dict, Hashable, Type, TypeVar
from typing_extensions import deprecated
[docs]
class NoPreviouslyCreatedInstanceError(Exception):
"""No previously created instance error"""
T = TypeVar("T")
[docs]
class CacheLast(type):
"""Metaclass that caches the last created instance"""
# _last_created_instance is a heterogeneous collection of type dict[Type[T], T] for
# the different T's that use this metaclass
_last_created_instance: Dict[Any, Any] = {}
def __call__(cls, *args, **kwargs):
# Create an instance
instance = super(CacheLast, cls).__call__(*args, **kwargs)
# Cache the instance
CacheLast._last_created_instance[cls] = instance
return instance
[docs]
def get_last_created(cls: Type[T]) -> T:
"""Get the last created instance"""
if cls not in CacheLast._last_created_instance:
raise NoPreviouslyCreatedInstanceError()
# Promise to the type system that we are fetching a T (which we are)
instance: T = CacheLast._last_created_instance[cls]
return instance
[docs]
class TSRandom:
"""
Generate random values that are not available from Python 3.7's random module.
"""
def __init__(self, *seed: Hashable):
self._is_seeded = False
self._random = _py_random.Random()
if len(seed) > 0:
self.seed(*seed)
[docs]
def seed(self, *content: Hashable) -> None:
"""
Seed the random number generator.
"""
content = (
(
part.encode()
if isinstance(part, str)
else str(hash(part)).encode()
if not isinstance(part, bytes)
else part
)
for part in content
)
try:
hash_ = sha512(next(content))
except StopIteration:
return
for part in content:
hash_.update(part)
rng_seed = hash_.digest()
self._random.seed(rng_seed)
self._is_seeded = True
[docs]
def randbytes(self, n_bytes: int) -> bytes:
"""Generate n random bytes."""
if hasattr(self._random, "randbytes"):
return self._random.randbytes(n_bytes)
# Adapted from the Python 3.9+ random.randbytes method.
return self._random.getrandbits(n_bytes * 8).to_bytes(n_bytes, "little")
[docs]
@deprecated(
"""This way of generating UUIDs is deprecated. Instead use:
- task_script_utils.uuid.uuid() for UUID generation
- mock_uuid_generator (fixture from ts-lib-pytest) for mocking UUID generation in tests
Note: ts-lib-pytest is a private TetraScience GitHub repository.
Please contact TetraScience if you want to use it or you can create your own UUID mocker)"""
)
def uuid(self) -> _uuid.UUID:
"""Create a pseudo random UUID"""
return _uuid.UUID(bytes=self.randbytes(16))
[docs]
class TaskScriptUUIDGenerator(TSRandom, metaclass=CacheLast):
"""UUID generator for use in Task Scripts"""
def __init__(self, task_script_identifier: str, file_identifier: Hashable):
super().__init__(task_script_identifier, file_identifier)
[docs]
@classmethod
def from_task_script_identifier_parts(
cls: Type[T], namespace: str, slug: str, version: str, file_identifier: Hashable
) -> T:
"""
A convenience method that takes the parts of a task script identifier (namespace, slug, and version) and an
identifier for the file (e.g. file location, file contents), then returns an instance of the generator class.
"""
return cls(f"{namespace}/{slug}:{version}", file_identifier)