Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/cardinal_pythonlib/randomness.py : 54%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# cardinal_pythonlib/randomness.py
4"""
5===============================================================================
7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com).
9 This file is part of cardinal_pythonlib.
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 https://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
23===============================================================================
25**Random number generation.**
27"""
29import base64
30import os
31from random import random as random_random
32import secrets
33import string
36# =============================================================================
37# Creating random strings
38# =============================================================================
40def create_base64encoded_randomness(num_bytes: int) -> str:
41 """
42 Create and return ``num_bytes`` of random data.
44 The result is encoded in a string with URL-safe ``base64`` encoding.
46 Used (for example) to generate session tokens.
48 Which generator to use? See
49 https://cryptography.io/en/latest/random-numbers/.
51 Do NOT use these methods:
53 .. code-block:: python
55 randbytes = M2Crypto.m2.rand_bytes(num_bytes) # NO!
56 randbytes = Crypto.Random.get_random_bytes(num_bytes) # NO!
58 Instead, do this:
60 .. code-block:: python
62 randbytes = os.urandom(num_bytes) # YES
63 """
64 randbytes = os.urandom(num_bytes) # YES
65 return base64.urlsafe_b64encode(randbytes).decode('ascii')
68def generate_random_string(length: int,
69 characters: str = None) -> str:
70 """
71 Generates a random string of the specified length.
72 """
73 characters = characters or (
74 string.ascii_letters + string.digits + string.punctuation
75 )
76 # We use secrets.choice() rather than random.choices() as it's better
77 # for security/cryptography purposes.
78 return "".join(secrets.choice(characters) for _ in range(length))
81# =============================================================================
82# Coin flips
83# =============================================================================
85def coin(p: float) -> bool:
86 """
87 Flips a biased coin; returns ``True`` or ``False``, with the specified
88 probability being that of ``True``.
89 """
90 # Slower code:
92 # assert 0 <= p <= 1
93 # r = random.random() # range [0.0, 1.0), i.e. 0 <= r < 1
94 # return r < p
96 # Check edge cases:
97 # - if p == 0, impossible that r < p, since r >= 0
98 # - if p == 1, always true that r < p, since r < 1
100 # Faster code:
102 return random_random() < p
105# =============================================================================
106# Testing
107# =============================================================================
109def _test_coin() -> None:
110 """
111 Tests the :func:`coin` function.
112 """
113 probabilities = [0, 0.25, 0.5, 0.75, 1]
114 n_values = [10, 1000, 1000000]
115 for p in probabilities:
116 for n in n_values:
117 coins = [1 if coin(p) else 0 for _ in range(n)]
118 s = sum(coins)
119 print(f"coin: p = {p}, n = {n} -> {s} true")
122if __name__ == '__main__':
123 _test_coin()