From 3e14e7c893da51a9567e9dac96e0d8b6b83d885a Mon Sep 17 00:00:00 2001 From: Alex Kanitz Date: Sat, 14 Nov 2020 16:48:25 +0200 Subject: [PATCH] feat(utils): generate random identifiers (#85) --- foca/__init__.py | 2 +- foca/utils/misc.py | 41 +++++++++++++++++++++++ tests/utils/test_misc.py | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 foca/utils/misc.py create mode 100644 tests/utils/test_misc.py diff --git a/foca/__init__.py b/foca/__init__.py index 2b8877c5..ef7eb44d 100644 --- a/foca/__init__.py +++ b/foca/__init__.py @@ -1 +1 @@ -__version__ = '0.5.0' +__version__ = '0.6.0' diff --git a/foca/utils/misc.py b/foca/utils/misc.py new file mode 100644 index 00000000..2fc6a09e --- /dev/null +++ b/foca/utils/misc.py @@ -0,0 +1,41 @@ +"""Miscellaneous utility functions.""" + +from random import choice +import string + + +def generate_id( + charset: str = ''.join([string.ascii_letters, string.digits]), + length: int = 6 +) -> str: + """Generate random string based on allowed set of characters. + + Args: + charset: A string of allowed characters or an expression evaluating to + a string of allowed characters. + length: Length of returned string. + + Returns: + Random string of specified length and composed of defined set of + allowed characters. + + Raises: + TypeError: Raised if 'charset' cannot be evaluated to a string or if + 'length' is not a positive integer. + """ + try: + charset = eval(charset) + except (NameError, SyntaxError): + pass + except Exception as e: + raise TypeError(f"Could not evaluate 'charset': {charset}") from e + if not isinstance(charset, str) or charset == "": + raise TypeError( + f"Could not evaluate 'charset' to non-empty string: {charset}" + ) + if not isinstance(length, int) or not length > 0: + raise TypeError( + f"Argument to 'length' is not a positive integer: {length}" + ) + charset = ''.join(sorted(set(charset))) + return ''.join(choice(charset) for __ in range(length)) diff --git a/tests/utils/test_misc.py b/tests/utils/test_misc.py new file mode 100644 index 00000000..a5f9a40e --- /dev/null +++ b/tests/utils/test_misc.py @@ -0,0 +1,70 @@ +"""Tests for miscellaneous utility functions.""" + +import string + +import pytest + +from foca.utils.misc import generate_id + + +class TestGenerateId: + + def test_default(self): + """Use only default arguments.""" + res = generate_id() + assert isinstance(res, str) + + def test_charset_literal_string(self): + """Argument to `charset` is non-default literal string.""" + charset = string.digits + res = generate_id(charset=charset) + assert set(res) <= set(string.digits) + + def test_charset_literal_string_duplicates(self): + """Argument to `charset` is non-default literal string with duplicates. + """ + charset = string.digits + string.digits + res = generate_id(charset=charset) + assert set(res) <= set(string.digits) + + def test_charset_evaluates_to_string(self): + """Argument to `charset` evaluates to string.""" + charset = "''.join([c for c in string.digits])" + res = generate_id(charset=charset) + assert set(res) <= set(string.digits) + + def test_charset_evaluates_to_empty_string(self): + """Argument to `charset` evaluates to non-string.""" + charset = "''.join([])" + with pytest.raises(TypeError): + generate_id(charset=charset) + + def test_charset_evaluates_to_non_string(self): + """Argument to `charset` evaluates to non-string.""" + charset = "1" + with pytest.raises(TypeError): + generate_id(charset=charset) + + def test_evaluation_error(self): + """Evaulation of `length` raises an exception.""" + charset = int + with pytest.raises(TypeError): + generate_id(charset=charset) # type: ignore + + def test_length(self): + """Non-default argument to `length`.""" + length = 1 + res = generate_id(length=length) + assert len(res) == length + + def test_length_not_int(self): + """Argument to `length` is not an integer.""" + length = "" + with pytest.raises(TypeError): + generate_id(length=length) # type: ignore + + def test_length_not_positive(self): + """Argument to `length` is not a positive integer.""" + length = -1 + with pytest.raises(TypeError): + generate_id(length=length)