From d1c619b8ed3722304c3944b48d4b6cd28a1662e6 Mon Sep 17 00:00:00 2001 From: Paul Prescod Date: Mon, 8 Nov 2021 12:52:55 -0800 Subject: [PATCH] Switch from 8-bits of randomness to a guaranteed unique counter (#529) --- docs/index.md | 10 +++++----- snowfakery/standard_plugins/UniqueId.py | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/index.md b/docs/index.md index 272d4f2d..4591ffc8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1408,7 +1408,7 @@ If you are using Snowfakery outside of CumulusCI, you can turn on Big ID mode from the command line: ```s -$ python -m snowfakery examples/test_unique_id.yml --plugin-option big_ids True +$ snowfakery examples/test_unique_id.yml --plugin-option big_ids True Contact(id=1, FirstName=Stephen, LastName=Parrish, Employee=763534209134265915391, Email=763534209134265915392_brittany17@example.org) ``` @@ -1418,7 +1418,7 @@ plugin option `pid`. Those execution ids would replace the timestamp and process information in your Big ID's calculation. ```s - $ python -m snowfakery examples/test_unique_id.yml --plugin-option big_ids True --plugin-option pid 111 + $ snowfakery examples/test_unique_id.yml --plugin-option big_ids True --plugin-option pid 111 Contact(id=1, FirstName=Melody, LastName=Marquez, Employee=157937691, Email=157937692_emmahoover@example.org) ``` @@ -1427,7 +1427,7 @@ the unique ID generation algorithm. As an example, you can inject about 15 digits of randomness and a timestamp like this on Unix-ish systems: ```s -python -m snowfakery examples/test_unique_id.yml --plugin-option big_ids True --plugin-option pid `date +"%s"`$RANDOM$RANDOM$RANDOM +snowfakery examples/test_unique_id.yml --plugin-option big_ids True --plugin-option pid `date +"%s"`$RANDOM$RANDOM$RANDOM Contact(id=1, FirstName=Cheryl, LastName=Estes, Employee=11014765223046647500920591, Email=11014765223046647500920592_solisroy@example.org) ``` @@ -1466,7 +1466,7 @@ Snowfakery can generate incrementing numbers like this: This would output: ```s -$ python -m snowfakery examples/counters/number_counter.recipe.yml +$ snowfakery examples/counters/number_counter.recipe.yml Example(id=1, count=1) Example(id=2, count=2) Example(id=3, count=3) @@ -1511,7 +1511,7 @@ Example(id=10, count=38) As described above, counters are reset each iteration, as described above: ```s -python -m snowfakery examples/counters/number_counter.recipe.yml --reps 2 +snowfakery examples/counters/number_counter.recipe.yml --reps 2 Example(id=1, count=1) Example(id=2, count=2) Example(id=3, count=3) diff --git a/snowfakery/standard_plugins/UniqueId.py b/snowfakery/standard_plugins/UniqueId.py index 227f9bce..3d277930 100644 --- a/snowfakery/standard_plugins/UniqueId.py +++ b/snowfakery/standard_plugins/UniqueId.py @@ -1,7 +1,6 @@ import os import time from itertools import count -import random from math import log import string @@ -116,6 +115,8 @@ def _oct(number): class UniqueNumericIdGenerator(PluginResult): + context_uniqifier = count(1) + def __init__( self, *, @@ -125,6 +126,7 @@ def __init__( randomize: bool = True, start: int = 1, ): + self.unique_identifer = next(self.context_uniqifier) self.counter = count(start) self.start = start self.parts = parts @@ -155,14 +157,17 @@ def _convert(self, part): part = part.lower() if part == "pid": return self.pid - elif part.startswith("rand"): - # note that rand is only evaluated once per generator! Not for every generation - numbits = int(part[4:]) - return _oct(random.getrandbits(numbits)) + # possible future feature: rand8, rand16, etc. + # elif part.startswith("rand"): + # # note that rand is only evaluated once per generator! Not for every generation + # numbits = int(part[4:]) + # return _oct(random.getrandbits(numbits)) elif part.isnumeric() or isinstance(part, int): return _oct(int(part)) elif part == "index": return "{index:o}" + elif part == "context": + return _oct(self.unique_identifer) else: raise exc.DataGenValueError(f"Unknown input to eval: {part}") @@ -274,7 +279,7 @@ def unique_id(self): def NumericIdGenerator(self, _=None, *, template: str = None): template = template or ( - "pid,rand8,index" if self._bigids else "rand8,index" + "pid,context,index" if self._bigids else "context,index" ) return UniqueNumericIdGenerator(pid=self._pid, parts=template) @@ -286,7 +291,7 @@ def AlphaCodeGenerator( randomize_codes: bool = True, ): alphabet = str(alphabet) if isinstance(alphabet, int) else alphabet - template = template or ("pid,rand8,index" if self._bigids else "index") + template = template or ("pid,context,index" if self._bigids else "index") return AlphaUniquifier( pid=self._pid,