Skip to content

Commit 6981b20

Browse files
committed
fixes #718
1 parent c9338c2 commit 6981b20

3 files changed

Lines changed: 104 additions & 6 deletions

File tree

fastcore/_modidx.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@
739739
'fastcore.xtras.trim_wraps': ('xtras.html#trim_wraps', 'fastcore/xtras.py'),
740740
'fastcore.xtras.truncstr': ('xtras.html#truncstr', 'fastcore/xtras.py'),
741741
'fastcore.xtras.type2str': ('xtras.html#type2str', 'fastcore/xtras.py'),
742+
'fastcore.xtras.unqid': ('xtras.html#unqid', 'fastcore/xtras.py'),
742743
'fastcore.xtras.untar_dir': ('xtras.html#untar_dir', 'fastcore/xtras.py'),
743744
'fastcore.xtras.utc2local': ('xtras.html#utc2local', 'fastcore/xtras.py'),
744745
'fastcore.xtras.vars_pub': ('xtras.html#vars_pub', 'fastcore/xtras.py'),

fastcore/xtras.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
'bunzip', 'loads', 'loads_multi', 'dumps', 'untar_dir', 'repo_details', 'shell', 'ssh', 'rsync_multi', 'run',
1111
'open_file', 'save_pickle', 'load_pickle', 'parse_env', 'expand_wildcards', 'dict2obj', 'obj2dict',
1212
'repr_dict', 'is_listy', 'mapped', 'IterLen', 'ReindexCollection', 'SaveReturn', 'trim_wraps', 'save_iter',
13-
'asave_iter', 'friendly_name', 'n_friendly_names', 'exec_eval', 'get_source_link', 'truncstr', 'sparkline',
14-
'modify_exception', 'round_multiple', 'set_num_threads', 'join_path_file', 'autostart', 'EventTimer',
15-
'stringfmt_names', 'PartialFormatter', 'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env',
16-
'ContextManagers', 'shufflish', 'console_help', 'hl_md', 'type2str', 'dataclass_src', 'Unset', 'nullable_dc',
17-
'make_nullable', 'flexiclass', 'asdict', 'vars_pub', 'is_typeddict', 'is_namedtuple', 'CachedIter',
18-
'CachedAwaitable', 'reawaitable', 'flexicache', 'time_policy', 'mtime_policy', 'timed_cache']
13+
'asave_iter', 'unqid', 'friendly_name', 'n_friendly_names', 'exec_eval', 'get_source_link', 'truncstr',
14+
'sparkline', 'modify_exception', 'round_multiple', 'set_num_threads', 'join_path_file', 'autostart',
15+
'EventTimer', 'stringfmt_names', 'PartialFormatter', 'partial_format', 'utc2local', 'local2utc', 'trace',
16+
'modified_env', 'ContextManagers', 'shufflish', 'console_help', 'hl_md', 'type2str', 'dataclass_src',
17+
'Unset', 'nullable_dc', 'make_nullable', 'flexiclass', 'asdict', 'vars_pub', 'is_typeddict', 'is_namedtuple',
18+
'CachedIter', 'CachedAwaitable', 'reawaitable', 'flexicache', 'time_policy', 'mtime_policy', 'timed_cache']
1919

2020
# %% ../nbs/03_xtras.ipynb
2121
from .imports import *
@@ -511,6 +511,15 @@ def asave_iter(g):
511511
def _(*args, **kwargs): return _save_iter(g, *args, **kwargs)
512512
return _
513513

514+
# %% ../nbs/03_xtras.ipynb
515+
def unqid(seeded=False):
516+
"Generate a unique id suitable for use as a Python identifier"
517+
from base64 import b64encode
518+
from uuid import uuid4,UUID
519+
id4 = UUID(int=random.getrandbits(128), version=4) if seeded else uuid4()
520+
res = b64encode(id4.bytes)
521+
return '_' + res.decode().rstrip('=').translate(str.maketrans('+/', '_-'))
522+
514523
# %% ../nbs/03_xtras.ipynb
515524
def friendly_name(levels=3, suffix=4):
516525
"Generate a random human-readable name with customizable word levels and suffix length"

nbs/03_xtras.ipynb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,6 +2393,94 @@
23932393
"## Other Helpers"
23942394
]
23952395
},
2396+
{
2397+
"cell_type": "code",
2398+
"execution_count": null,
2399+
"id": "35368de5",
2400+
"metadata": {},
2401+
"outputs": [],
2402+
"source": [
2403+
"#| export\n",
2404+
"def unqid(seeded=False):\n",
2405+
" \"Generate a unique id suitable for use as a Python identifier\"\n",
2406+
" from base64 import b64encode\n",
2407+
" from uuid import uuid4,UUID\n",
2408+
" id4 = UUID(int=random.getrandbits(128), version=4) if seeded else uuid4()\n",
2409+
" res = b64encode(id4.bytes)\n",
2410+
" return '_' + res.decode().rstrip('=').translate(str.maketrans('+/', '_-'))"
2411+
]
2412+
},
2413+
{
2414+
"cell_type": "markdown",
2415+
"id": "d193424a",
2416+
"metadata": {},
2417+
"source": [
2418+
"`unqid` generates a random unique identifier that is safe to use as a Python variable name (starts with `_`, uses only alphanumeric characters and underscores). It's based on UUID4, encoded in URL-safe base64.\n",
2419+
"\n",
2420+
"If `seeded=True`, uses `random.getrandbits` which respects `random.seed()`, making it reproducible. Otherwise uses `uuid4()` which is always random."
2421+
]
2422+
},
2423+
{
2424+
"cell_type": "code",
2425+
"execution_count": null,
2426+
"id": "8e065069",
2427+
"metadata": {},
2428+
"outputs": [
2429+
{
2430+
"data": {
2431+
"text/plain": [
2432+
"'_XZ1jJo-rS4yr1Qz6prx-fg'"
2433+
]
2434+
},
2435+
"execution_count": null,
2436+
"metadata": {},
2437+
"output_type": "execute_result"
2438+
}
2439+
],
2440+
"source": [
2441+
"unqid()"
2442+
]
2443+
},
2444+
{
2445+
"cell_type": "markdown",
2446+
"id": "662f49fa",
2447+
"metadata": {},
2448+
"source": [
2449+
"With seeding for reproducibility:"
2450+
]
2451+
},
2452+
{
2453+
"cell_type": "code",
2454+
"execution_count": null,
2455+
"id": "b37f4eed",
2456+
"metadata": {},
2457+
"outputs": [],
2458+
"source": [
2459+
"random.seed(42)\n",
2460+
"a = unqid(seeded=True)\n",
2461+
"random.seed(42)\n",
2462+
"b = unqid(seeded=True)\n",
2463+
"test_eq(a, b)"
2464+
]
2465+
},
2466+
{
2467+
"cell_type": "markdown",
2468+
"id": "db2843d8",
2469+
"metadata": {},
2470+
"source": [
2471+
"Without seeding - always unique:"
2472+
]
2473+
},
2474+
{
2475+
"cell_type": "code",
2476+
"execution_count": null,
2477+
"id": "355ab870",
2478+
"metadata": {},
2479+
"outputs": [],
2480+
"source": [
2481+
"test_ne(unqid(), unqid())"
2482+
]
2483+
},
23962484
{
23972485
"cell_type": "code",
23982486
"execution_count": null,

0 commit comments

Comments
 (0)