Skip to content

Commit 3e14e7c

Browse files
authored
feat(utils): generate random identifiers (#85)
1 parent 7b35ec3 commit 3e14e7c

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

foca/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.5.0'
1+
__version__ = '0.6.0'

foca/utils/misc.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Miscellaneous utility functions."""
2+
3+
from random import choice
4+
import string
5+
6+
7+
def generate_id(
8+
charset: str = ''.join([string.ascii_letters, string.digits]),
9+
length: int = 6
10+
) -> str:
11+
"""Generate random string based on allowed set of characters.
12+
13+
Args:
14+
charset: A string of allowed characters or an expression evaluating to
15+
a string of allowed characters.
16+
length: Length of returned string.
17+
18+
Returns:
19+
Random string of specified length and composed of defined set of
20+
allowed characters.
21+
22+
Raises:
23+
TypeError: Raised if 'charset' cannot be evaluated to a string or if
24+
'length' is not a positive integer.
25+
"""
26+
try:
27+
charset = eval(charset)
28+
except (NameError, SyntaxError):
29+
pass
30+
except Exception as e:
31+
raise TypeError(f"Could not evaluate 'charset': {charset}") from e
32+
if not isinstance(charset, str) or charset == "":
33+
raise TypeError(
34+
f"Could not evaluate 'charset' to non-empty string: {charset}"
35+
)
36+
if not isinstance(length, int) or not length > 0:
37+
raise TypeError(
38+
f"Argument to 'length' is not a positive integer: {length}"
39+
)
40+
charset = ''.join(sorted(set(charset)))
41+
return ''.join(choice(charset) for __ in range(length))

tests/utils/test_misc.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Tests for miscellaneous utility functions."""
2+
3+
import string
4+
5+
import pytest
6+
7+
from foca.utils.misc import generate_id
8+
9+
10+
class TestGenerateId:
11+
12+
def test_default(self):
13+
"""Use only default arguments."""
14+
res = generate_id()
15+
assert isinstance(res, str)
16+
17+
def test_charset_literal_string(self):
18+
"""Argument to `charset` is non-default literal string."""
19+
charset = string.digits
20+
res = generate_id(charset=charset)
21+
assert set(res) <= set(string.digits)
22+
23+
def test_charset_literal_string_duplicates(self):
24+
"""Argument to `charset` is non-default literal string with duplicates.
25+
"""
26+
charset = string.digits + string.digits
27+
res = generate_id(charset=charset)
28+
assert set(res) <= set(string.digits)
29+
30+
def test_charset_evaluates_to_string(self):
31+
"""Argument to `charset` evaluates to string."""
32+
charset = "''.join([c for c in string.digits])"
33+
res = generate_id(charset=charset)
34+
assert set(res) <= set(string.digits)
35+
36+
def test_charset_evaluates_to_empty_string(self):
37+
"""Argument to `charset` evaluates to non-string."""
38+
charset = "''.join([])"
39+
with pytest.raises(TypeError):
40+
generate_id(charset=charset)
41+
42+
def test_charset_evaluates_to_non_string(self):
43+
"""Argument to `charset` evaluates to non-string."""
44+
charset = "1"
45+
with pytest.raises(TypeError):
46+
generate_id(charset=charset)
47+
48+
def test_evaluation_error(self):
49+
"""Evaulation of `length` raises an exception."""
50+
charset = int
51+
with pytest.raises(TypeError):
52+
generate_id(charset=charset) # type: ignore
53+
54+
def test_length(self):
55+
"""Non-default argument to `length`."""
56+
length = 1
57+
res = generate_id(length=length)
58+
assert len(res) == length
59+
60+
def test_length_not_int(self):
61+
"""Argument to `length` is not an integer."""
62+
length = ""
63+
with pytest.raises(TypeError):
64+
generate_id(length=length) # type: ignore
65+
66+
def test_length_not_positive(self):
67+
"""Argument to `length` is not a positive integer."""
68+
length = -1
69+
with pytest.raises(TypeError):
70+
generate_id(length=length)

0 commit comments

Comments
 (0)