I would prefer urandom over secrets.token_hex, as it samples from a richer character set and hence needs a smaller length to achieve the same entropy.
os.urandom, which reads from urandom, is considered secure (see the relevant answer in a question if urandom is secure). You can then read as much as you like from urandom and produce a random alphanummeric as follows:
import math
import os
def random_alphanumeric(str_len: int) -> str:
  rand_len = 3 * (math.ceil(str_len / 3) + 1)
  return base64.b64encode(os.urandom(rand_len), altchars=b'aA').decode('ascii')[:str_len]
NOTE: The above function is not secure. Since you need a "very quick way to generate an alphanumeric", this function sacrifices performance over security, since the frequencies of a and A (or whatever characters you choose to replace + and / with)  will be increased compared to what urandom would give you otherwise.
If you put randomness above  performance, you could do something like:
def secure_random_alphanumeric(str_len: int) -> str:
  ret = ''
  while len(ret) < str_len:
    rand_len = 3 * (math.ceil((str_len - len(ret)) / 3) + 2)
    ret += base64.b64encode(os.urandom(rand_len)).decode('ascii').replace('+', '').replace('/', '').replace('=', '')
  return ret[:str_len]
Note that chaining replace turns out to be faster than sequntially calling it, as per this answer.
Also, in the above, +1 is replaced by +2 when determining  rand_lento reduce the number of iterations needed to achieve the requested length. You could even replace by +3 or more to reduce even more the possibility for an iteration, but then you would loose in performance at the chained replace calls.