Skip to content

Commit

Permalink
feat(utils): generate random identifiers (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg authored Nov 14, 2020
1 parent 7b35ec3 commit 3e14e7c
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 1 deletion.
2 changes: 1 addition & 1 deletion foca/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.5.0'
__version__ = '0.6.0'
41 changes: 41 additions & 0 deletions foca/utils/misc.py
Original file line number Diff line number Diff line change
@@ -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))
70 changes: 70 additions & 0 deletions tests/utils/test_misc.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 3e14e7c

Please sign in to comment.