cuid2 2.0.2__tar.gz → 2.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuid2
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance.
5
5
  Keywords: crypt,security,uuid,guid,cuid,cryptography
6
6
  Author: Will Gordon
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "cuid2"
7
- version = "2.0.2"
7
+ version = "2.0.4"
8
8
  description = "Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "cuid2"
7
- version = "2.0.2"
7
+ version = "2.0.4"
8
8
  authors = [
9
9
  {name = "Will Gordon", email = "dev@gordoncode.dev"},
10
10
  ]
@@ -0,0 +1,21 @@
1
+ """Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."""
2
+
3
+ from .generator import (
4
+ BIG_LENGTH,
5
+ DEFAULT_LENGTH,
6
+ INITIAL_COUNT_MAX,
7
+ MAXIMUM_LENGTH,
8
+ MIN_LENGTH,
9
+ Cuid,
10
+ cuid_wrapper,
11
+ )
12
+
13
+ __all__ = [
14
+ "BIG_LENGTH",
15
+ "DEFAULT_LENGTH",
16
+ "INITIAL_COUNT_MAX",
17
+ "MAXIMUM_LENGTH",
18
+ "MIN_LENGTH",
19
+ "Cuid",
20
+ "cuid_wrapper",
21
+ ]
@@ -1,12 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import inspect
3
4
  import re
4
5
  import time
6
+ import warnings
5
7
  from math import floor
6
8
  from secrets import SystemRandom
7
9
  from typing import TYPE_CHECKING, Callable, Final, Optional, Protocol
8
10
 
9
11
  from cuid2 import utils
12
+ from cuid2.utils import BIG_LENGTH, DEFAULT_LENGTH, INITIAL_COUNT_MAX, MAXIMUM_LENGTH, MIN_LENGTH
10
13
 
11
14
  if TYPE_CHECKING:
12
15
  from _random import Random
@@ -15,14 +18,47 @@ if TYPE_CHECKING:
15
18
  def __call__(self: FingerprintCallable, random_generator: Random) -> str: ...
16
19
 
17
20
 
18
- # ~22k hosts before 50% chance of initial counter collision
19
- # with a remaining counter range of 9.0e+15 in JavaScript.
20
- INITIAL_COUNT_MAX: Final[int] = 476782367
21
- DEFAULT_LENGTH: Final = 24
22
- MAXIMUM_LENGTH: Final = 98
23
21
  _CUID_REGEX: Final[re.Pattern[str]] = re.compile(r"^[a-z][0-9a-z]+\Z")
24
22
 
25
23
 
24
+ def _validate_length(length: int) -> None:
25
+ """Validate that the requested CUID length is within supported bounds.
26
+
27
+ Parameters
28
+ ----------
29
+ length : int
30
+ The requested CUID length to validate.
31
+
32
+ Raises
33
+ ------
34
+ ValueError
35
+ If `length` is a boolean, less than 1, or greater than `MAXIMUM_LENGTH` (98).
36
+
37
+ Warns
38
+ -----
39
+ DeprecationWarning
40
+ If `length` is outside the CUID2 specification range (`MIN_LENGTH`..`BIG_LENGTH`, i.e., 2..32).
41
+ """
42
+ if isinstance(length, bool) or length < 1:
43
+ msg = "Length must be at least 1 character."
44
+ raise ValueError(msg)
45
+ if length > MAXIMUM_LENGTH:
46
+ msg = "Length must never exceed 98 characters."
47
+ raise ValueError(msg)
48
+ if length < MIN_LENGTH or length > BIG_LENGTH:
49
+ stacklevel: int = 1
50
+ frame = inspect.currentframe()
51
+ while frame is not None and frame.f_code.co_filename == __file__:
52
+ stacklevel += 1
53
+ frame = frame.f_back
54
+ warnings.warn(
55
+ f"Length {length} outside the CUID2 specification range ({MIN_LENGTH}..{BIG_LENGTH}) "
56
+ "is deprecated and will raise ValueError in v3.0.0.",
57
+ DeprecationWarning,
58
+ stacklevel=stacklevel,
59
+ )
60
+
61
+
26
62
  class Cuid: # pylint: disable=too-few-public-methods
27
63
  def __init__(
28
64
  self: Cuid,
@@ -46,20 +82,21 @@ class Cuid: # pylint: disable=too-few-public-methods
46
82
  The length parameter is an integer that determines the maximum length of the generated string.
47
83
  It has a default value of DEFAULT_LENGTH (24).
48
84
  A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
85
+ Lengths outside `MIN_LENGTH`..`BIG_LENGTH` (2..32) emit a DeprecationWarning.
49
86
  fingerprint : "FingerprintCallable", default=utils.create_fingerprint
50
87
  The "fingerprint" parameter is a callable function that generates a unique identifier.
51
88
 
52
89
  Raises
53
90
  ------
54
91
  ValueError
55
- If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
92
+ If the length parameter is a boolean, less than 1, or greater than `MAXIMUM_LENGTH` (98 characters).
93
+
94
+ Warns
95
+ -----
96
+ DeprecationWarning
97
+ If the length parameter is outside `MIN_LENGTH`..`BIG_LENGTH` (2..32 characters).
56
98
  """
57
- if isinstance(length, bool) or length < 1:
58
- msg = "Length must be at least 1 character."
59
- raise ValueError(msg)
60
- if length > MAXIMUM_LENGTH:
61
- msg = "Length must never exceed 98 characters."
62
- raise ValueError(msg)
99
+ _validate_length(length)
63
100
 
64
101
  self._random: Random = random_generator()
65
102
  self._counter: Callable[[], int] = counter(floor(self._random.random() * INITIAL_COUNT_MAX))
@@ -75,6 +112,7 @@ class Cuid: # pylint: disable=too-few-public-methods
75
112
  The length parameter is an optional integer value that specifies the length of the generated string.
76
113
  If it is not provided, the default length value provided during class initialization is used.
77
114
  A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
115
+ Lengths outside `MIN_LENGTH`..`BIG_LENGTH` (2..32) emit a DeprecationWarning.
78
116
 
79
117
  Returns
80
118
  -------
@@ -86,15 +124,16 @@ class Cuid: # pylint: disable=too-few-public-methods
86
124
  Raises
87
125
  ------
88
126
  ValueError
89
- If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
127
+ If the length parameter is a boolean, less than 1, or greater than `MAXIMUM_LENGTH` (98 characters).
128
+
129
+ Warns
130
+ -----
131
+ DeprecationWarning
132
+ If the length parameter is outside `MIN_LENGTH`..`BIG_LENGTH` (2..32 characters).
90
133
  """
134
+ if length is not None:
135
+ _validate_length(length)
91
136
  resolved_length: int = self._length if length is None else length
92
- if isinstance(resolved_length, bool) or resolved_length < 1:
93
- msg = "Length must be at least 1 character."
94
- raise ValueError(msg)
95
- if resolved_length > MAXIMUM_LENGTH:
96
- msg = "Length must never exceed 98 characters."
97
- raise ValueError(msg)
98
137
 
99
138
  first_letter: str = utils.create_letter(random_generator=self._random)
100
139
 
@@ -145,6 +184,7 @@ def cuid_wrapper(length: int = DEFAULT_LENGTH) -> Callable[[], str]:
145
184
  The length parameter is an integer that determines the maximum length of the generated string.
146
185
  It has a default value of DEFAULT_LENGTH (24).
147
186
  A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
187
+ Lengths outside `MIN_LENGTH`..`BIG_LENGTH` (2..32) emit a DeprecationWarning.
148
188
 
149
189
  Returns
150
190
  -------
@@ -154,7 +194,12 @@ def cuid_wrapper(length: int = DEFAULT_LENGTH) -> Callable[[], str]:
154
194
  Raises
155
195
  ------
156
196
  ValueError
157
- If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
197
+ If the length parameter is a boolean, less than 1, or greater than `MAXIMUM_LENGTH` (98 characters).
198
+
199
+ Warns
200
+ -----
201
+ DeprecationWarning
202
+ If the length parameter is outside `MIN_LENGTH`..`BIG_LENGTH` (2..32 characters).
158
203
  """
159
204
  cuid_generator: Cuid = Cuid(length=length)
160
205
 
@@ -3,19 +3,22 @@ from __future__ import annotations
3
3
  import os
4
4
  import socket
5
5
  import string
6
+ from hashlib import sha3_512
6
7
  from math import floor
7
8
  from typing import TYPE_CHECKING, Callable, Final, Optional
8
9
 
9
- try:
10
- from hashlib import sha3_512 as sha512
11
- except ImportError:
12
- import warnings
10
+ # ~22k hosts before 50% chance of initial counter collision
11
+ # with a remaining counter range of 9.0e+15 in JavaScript.
12
+ INITIAL_COUNT_MAX: Final[int] = 476782367
13
+ MIN_LENGTH: Final[int] = 2
14
+ DEFAULT_LENGTH: Final[int] = 24
15
+ BIG_LENGTH: Final[int] = 32
16
+ MAXIMUM_LENGTH: Final[int] = 98
13
17
 
14
- warnings.warn("sha3_512 is not available, falling back to sha512, this is less secure!", UserWarning, stacklevel=2)
15
- from hashlib import sha512 # type: ignore[assignment] # pylint: disable=ungrouped-imports
16
-
17
-
18
- BIG_LENGTH: Final = 32
18
+ ASCII_LOWERCASE: Final[str] = string.ascii_lowercase
19
+ ASCII_LOWERCASE_LEN: Final[int] = len(ASCII_LOWERCASE)
20
+ BASE36_ALPHABET: Final[str] = string.digits + string.ascii_lowercase
21
+ BASE36_RADIX: Final[int] = len(BASE36_ALPHABET)
19
22
 
20
23
 
21
24
  if TYPE_CHECKING:
@@ -103,16 +106,11 @@ def create_entropy(random_generator: Random, length: int = 4) -> str:
103
106
  msg = "Cannot create entropy without a length >= 1."
104
107
  raise ValueError(msg)
105
108
 
106
- entropy: str = ""
107
-
108
- while len(entropy) < length:
109
- entropy += base36_encode(floor(random_generator.random() * 36))
110
-
111
- return entropy
109
+ return "".join([BASE36_ALPHABET[floor(random_generator.random() * BASE36_RADIX)] for _ in range(length)])
112
110
 
113
111
 
114
112
  def create_hash(data: str = "") -> str:
115
- """Creates a hash value for a given string using the SHA-512 algorithm (prefers SHA3) and returns
113
+ """Creates a hash value for a given string using the SHA3-512 algorithm and returns
116
114
  it in base36 encoding format after dropping the first character.
117
115
 
118
116
  Parameters
@@ -124,10 +122,10 @@ def create_hash(data: str = "") -> str:
124
122
  Returns
125
123
  -------
126
124
  str
127
- Base36 encoding of the SHA-512 hash of the input string `data`, with the first character dropped.
125
+ Base36 encoding of the SHA3-512 hash of the input string `data`, with the first character dropped.
128
126
 
129
127
  """
130
- hashed_value: _Hash = sha512(data.encode())
128
+ hashed_value: _Hash = sha3_512(data.encode())
131
129
  hashed_int: int = int.from_bytes(hashed_value.digest(), byteorder="big")
132
130
 
133
131
  # Drop the first character because it will bias the histogram to the left.
@@ -147,8 +145,7 @@ def create_letter(random_generator: Random) -> str:
147
145
  str
148
146
  a randomly generated lowercase letter from the English alphabet.
149
147
  """
150
- alphabet: str = string.ascii_lowercase
151
- return alphabet[floor(random_generator.random() * len(alphabet))]
148
+ return ASCII_LOWERCASE[floor(random_generator.random() * ASCII_LOWERCASE_LEN)]
152
149
 
153
150
 
154
151
  def base36_encode(number: int) -> str:
@@ -175,12 +172,13 @@ def base36_encode(number: int) -> str:
175
172
  msg = "Cannot encode negative integers."
176
173
  raise ValueError(msg)
177
174
 
178
- encoded_string: str = ""
179
- alphabet: str = string.digits + string.ascii_lowercase
180
- alphabet_length: int = len(alphabet)
175
+ if number == 0:
176
+ return "0"
181
177
 
178
+ encoded_chars: list[str] = []
182
179
  while number != 0:
183
- number, mod = divmod(number, alphabet_length)
184
- encoded_string = alphabet[mod] + encoded_string
180
+ number, mod = divmod(number, BASE36_RADIX)
181
+ encoded_chars.append(BASE36_ALPHABET[mod])
185
182
 
186
- return encoded_string or "0"
183
+ encoded_chars.reverse()
184
+ return "".join(encoded_chars)
@@ -1,5 +0,0 @@
1
- """Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."""
2
-
3
- from .generator import DEFAULT_LENGTH, INITIAL_COUNT_MAX, Cuid, cuid_wrapper
4
-
5
- __all__ = ["DEFAULT_LENGTH", "INITIAL_COUNT_MAX", "Cuid", "cuid_wrapper"]
File without changes
File without changes
File without changes
File without changes