cuid2 2.0.1__tar.gz → 2.0.2__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,48 +1,32 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cuid2
3
- Version: 2.0.1
3
+ Version: 2.0.2
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
- Author-Email: Will Gordon <wgordon@redhat.com>
7
- License: The MIT License (MIT)
8
-
9
- Copyright (c) 2023 Gordon Code
10
-
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to deal
13
- in the Software without restriction, including without limitation the rights
14
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
- copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
17
-
18
- The above copyright notice and this permission notice shall be included in all
19
- copies or substantial portions of the Software.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
- SOFTWARE.
6
+ Author: Will Gordon
7
+ Author-email: Will Gordon <dev@gordoncode.dev>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
28
10
  Classifier: Development Status :: 5 - Production/Stable
29
11
  Classifier: Intended Audience :: Developers
30
- Classifier: License :: OSI Approved :: MIT License
31
12
  Classifier: Operating System :: OS Independent
32
13
  Classifier: Programming Language :: Python :: 3
33
- Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
34
19
  Classifier: Topic :: Security :: Cryptography
35
20
  Classifier: Typing :: Typed
36
- Project-URL: Repository, https://github.com/gordon-code/cuid2/
37
- Requires-Python: >=3.8
21
+ Requires-Python: >=3.10
22
+ Project-URL: repository, https://github.com/gordon-code/cuid2/
38
23
  Description-Content-Type: text/markdown
39
24
 
40
25
  # cuid2
41
26
 
42
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://img.shields.io/badge/python-3.8+-blue.svg)
43
- [![PyPi](https://img.shields.io/pypi/v/cuid2.svg)](https://pypi.python.org/pypi/cuid2)
44
- [![PyPi](https://img.shields.io/pypi/dm/cuid2.svg)](https://pypi.python.org/pypi/cuid2)
45
- [![cuid2](https://snyk.io/advisor/python/cuid2/badge.svg)](https://snyk.io/advisor/python/cuid2)
27
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/cuid2/)
28
+ [![PyPi](https://img.shields.io/pypi/v/cuid2.svg)](https://pypi.org/project/cuid2/)
29
+ [![PyPi](https://img.shields.io/pypi/dm/cuid2.svg)](https://pypi.org/project/cuid2/)
46
30
 
47
31
  CUID2 for Python 3. Next generation GUIDs. Collision-resistant ids optimized for
48
32
  horizontal scaling and performance.
@@ -79,21 +63,24 @@ pip install cuid2
79
63
 
80
64
  ## Usage
81
65
 
82
- You can generate CUIDs directly in the terminal with the following:
66
+ You can generate CUIDs directly in the terminal with the following (optionally specifying a custom length):
83
67
  ```bash
84
68
  $ cuid2
69
+ $ cuid2 10
85
70
  ```
86
71
 
87
- Or you can rely on a CUID wrapper if you don't need any customizations:
72
+ Or you can rely on a CUID wrapper if you only need default or custom-length generation:
88
73
  ```python
89
74
  from typing import Callable
90
75
  from cuid2 import cuid_wrapper
91
76
 
92
77
  cuid_generator: Callable[[], str] = cuid_wrapper()
78
+ short_cuid_generator: Callable[[], str] = cuid_wrapper(length=10)
79
+
93
80
 
94
81
  def main():
95
- my_cuid: str = cuid_generator()
96
- next_cuid: str = cuid_generator()
82
+ my_cuid: str = cuid_generator()
83
+ short_cuid: str = short_cuid_generator()
97
84
  ```
98
85
 
99
86
  Finally, for more explicit control of the CUID generator, you can instantiate the class directly:
@@ -102,7 +89,14 @@ from cuid2 import Cuid
102
89
 
103
90
  CUID_GENERATOR: Cuid = Cuid(length=10)
104
91
 
92
+
105
93
  def main():
106
- my_cuid: str = CUID_GENERATOR.generate()
107
- next_cuid: str = CUID_GENERATOR.generate()
94
+ my_cuid: str = CUID_GENERATOR.generate()
95
+ next_cuid: str = CUID_GENERATOR.generate()
96
+
97
+ # Validate CUID
98
+ print(Cuid.is_cuid(my_cuid)) # True
108
99
  ```
100
+
101
+ `Cuid.is_cuid` also accepts optional `min_length` and `max_length` arguments to tighten the allowed length.
102
+ The bounds are clamped to 2 and 98 characters, so they can narrow but never widen what is considered valid.
@@ -1,9 +1,8 @@
1
1
  # cuid2
2
2
 
3
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://img.shields.io/badge/python-3.8+-blue.svg)
4
- [![PyPi](https://img.shields.io/pypi/v/cuid2.svg)](https://pypi.python.org/pypi/cuid2)
5
- [![PyPi](https://img.shields.io/pypi/dm/cuid2.svg)](https://pypi.python.org/pypi/cuid2)
6
- [![cuid2](https://snyk.io/advisor/python/cuid2/badge.svg)](https://snyk.io/advisor/python/cuid2)
3
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/cuid2/)
4
+ [![PyPi](https://img.shields.io/pypi/v/cuid2.svg)](https://pypi.org/project/cuid2/)
5
+ [![PyPi](https://img.shields.io/pypi/dm/cuid2.svg)](https://pypi.org/project/cuid2/)
7
6
 
8
7
  CUID2 for Python 3. Next generation GUIDs. Collision-resistant ids optimized for
9
8
  horizontal scaling and performance.
@@ -40,21 +39,24 @@ pip install cuid2
40
39
 
41
40
  ## Usage
42
41
 
43
- You can generate CUIDs directly in the terminal with the following:
42
+ You can generate CUIDs directly in the terminal with the following (optionally specifying a custom length):
44
43
  ```bash
45
44
  $ cuid2
45
+ $ cuid2 10
46
46
  ```
47
47
 
48
- Or you can rely on a CUID wrapper if you don't need any customizations:
48
+ Or you can rely on a CUID wrapper if you only need default or custom-length generation:
49
49
  ```python
50
50
  from typing import Callable
51
51
  from cuid2 import cuid_wrapper
52
52
 
53
53
  cuid_generator: Callable[[], str] = cuid_wrapper()
54
+ short_cuid_generator: Callable[[], str] = cuid_wrapper(length=10)
55
+
54
56
 
55
57
  def main():
56
- my_cuid: str = cuid_generator()
57
- next_cuid: str = cuid_generator()
58
+ my_cuid: str = cuid_generator()
59
+ short_cuid: str = short_cuid_generator()
58
60
  ```
59
61
 
60
62
  Finally, for more explicit control of the CUID generator, you can instantiate the class directly:
@@ -63,7 +65,14 @@ from cuid2 import Cuid
63
65
 
64
66
  CUID_GENERATOR: Cuid = Cuid(length=10)
65
67
 
68
+
66
69
  def main():
67
- my_cuid: str = CUID_GENERATOR.generate()
68
- next_cuid: str = CUID_GENERATOR.generate()
70
+ my_cuid: str = CUID_GENERATOR.generate()
71
+ next_cuid: str = CUID_GENERATOR.generate()
72
+
73
+ # Validate CUID
74
+ print(Cuid.is_cuid(my_cuid)) # True
69
75
  ```
76
+
77
+ `Cuid.is_cuid` also accepts optional `min_length` and `max_length` arguments to tighten the allowed length.
78
+ The bounds are clamped to 2 and 98 characters, so they can narrow but never widen what is considered valid.
@@ -1,25 +1,25 @@
1
1
  [build-system]
2
- requires = [
3
- "pdm-backend",
4
- ]
5
- build-backend = "pdm.backend"
2
+ requires = ["uv_build>=0.7.0,<0.14.0"]
3
+ build-backend = "uv_build"
6
4
 
7
5
  [project]
8
6
  name = "cuid2"
9
- dynamic = []
10
- authors = [
11
- { name = "Will Gordon", email = "wgordon@redhat.com" },
12
- ]
7
+ version = "2.0.2"
13
8
  description = "Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."
14
9
  readme = "README.md"
15
- requires-python = ">=3.8"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
16
13
  classifiers = [
17
14
  "Development Status :: 5 - Production/Stable",
18
15
  "Intended Audience :: Developers",
19
- "License :: OSI Approved :: MIT License",
20
16
  "Operating System :: OS Independent",
21
17
  "Programming Language :: Python :: 3",
22
- "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
23
  "Topic :: Security :: Cryptography",
24
24
  "Typing :: Typed",
25
25
  ]
@@ -31,10 +31,10 @@ keywords = [
31
31
  "cuid",
32
32
  "cryptography",
33
33
  ]
34
- version = "2.0.1"
35
34
 
36
- [project.license]
37
- file = "LICENSE"
35
+ [[project.authors]]
36
+ name = "Will Gordon"
37
+ email = "dev@gordoncode.dev"
38
38
 
39
39
  [project.urls]
40
40
  repository = "https://github.com/gordon-code/cuid2/"
@@ -42,72 +42,48 @@ repository = "https://github.com/gordon-code/cuid2/"
42
42
  [project.scripts]
43
43
  cuid2 = "cuid2.cli:main"
44
44
 
45
- [tool.pdm.version]
46
- source = "scm"
47
-
48
- [tool.pdm.scripts]
49
- ruff-check = "ruff check --fix --exit-zero {args:src/ local/tests/}"
50
- ruff-format = "ruff format {args:src/ local/tests/}"
51
- spelling = "codespell {args:src/ local/tests/}"
52
- pylint = "pylint {args:src/ local/tests/}"
53
- safety = "safety {args:check --bare}"
54
- typing = "mypy {args:src/ local/tests/}"
55
- update-pip = "pip install --upgrade pip"
56
- testing = "pytest local/tests"
57
- testing-slow = "pytest local/tests --runslow"
58
- tox = "tox --parallel auto"
59
-
60
- [tool.pdm.scripts.ruff-fix]
61
- composite = [
62
- "ruff-check",
63
- "ruff-format",
64
- ]
65
-
66
- [tool.pdm.scripts.lint-full]
67
- composite = [
68
- "ruff-fix",
69
- "spelling",
70
- "pylint",
45
+ [tool.uv]
46
+ default-groups = [
47
+ "lint",
48
+ "test",
49
+ "tox",
71
50
  "typing",
72
- "safety",
73
- ]
74
-
75
- [tool.pdm.scripts.tox-full]
76
- composite = [
77
- "update-pip",
78
- "lint-full",
79
- ]
80
-
81
- [tool.pdm.dev-dependencies]
82
- lint = [
83
- "codespell~=2.2.6",
84
- "pylint~=3.1.0",
85
- "requests>=2.31.0",
86
- "ruff~=0.3.7",
87
- "safety==3.1.0",
88
- ]
89
- test = [
90
- "pytest~=8.1.1",
91
- "pytest-mock~=3.14.0",
92
- "pytest-sugar~=1.0.0",
93
- ]
94
- tox = [
95
- "tox~=4.14.2",
96
- "tox-pdm~=0.7.2",
97
- ]
98
- typing = [
99
- "mypy~=1.9.0",
100
51
  ]
101
52
 
102
53
  [tool.tox]
103
- legacy_tox_ini = " [tox]\n min_version = 4\n env_list = py3{8,9,10,11,12}, check\n work_dir = local/.tox\n isolated_build = True\n\n [testenv]\n description = run unit tests\n groups = test\n commands = testing\n\n [testenv:check]\n description = run linters and typing\n skip_install = true\n groups = lint, typing, test\n commands = tox-full\n"
54
+ legacy_tox_ini = """
55
+ [tox]
56
+ min_version = 4
57
+ env_list = py3{10,11,12,13,14}, check
58
+ work_dir = local/.tox
59
+ isolated_build = True
60
+
61
+ [testenv]
62
+ runner = uv-venv-lock-runner
63
+ description = run unit tests
64
+ dependency_groups = test
65
+ commands = pytest local/tests
66
+
67
+ [testenv:check]
68
+ runner = uv-venv-lock-runner
69
+ description = run linters and typing
70
+ package = skip
71
+ dependency_groups =
72
+ lint
73
+ typing
74
+ test
75
+ commands =
76
+ ruff check src/ local/tests/
77
+ ruff format --check src/ local/tests/
78
+ codespell src/ local/tests/
79
+ pylint src/ local/tests/
80
+ mypy src/ local/tests/
81
+ """
104
82
 
105
83
  [tool.ruff]
106
84
  line-length = 120
107
- src = [
108
- "src",
109
- ]
110
- target-version = "py38"
85
+ src = ["src"]
86
+ target-version = "py310"
111
87
  cache-dir = "local/.ruff_cache"
112
88
  force-exclude = true
113
89
 
@@ -120,7 +96,6 @@ select = [
120
96
  "I",
121
97
  "N",
122
98
  "ASYNC",
123
- "TRIO",
124
99
  "S",
125
100
  "BLE",
126
101
  "B",
@@ -179,9 +154,7 @@ unfixable = [
179
154
  ]
180
155
 
181
156
  [tool.ruff.lint.isort]
182
- known-first-party = [
183
- "src",
184
- ]
157
+ known-first-party = ["src"]
185
158
  section-order = [
186
159
  "future",
187
160
  "standard-library",
@@ -197,6 +170,7 @@ convention = "numpy"
197
170
  minversion = "8.1"
198
171
  cache_dir = "local/.pytest_cache"
199
172
  python_files = "test_*.py"
173
+ testpaths = ["local/tests"]
200
174
 
201
175
  [tool.pylint.master]
202
176
  ignore-patterns = "test_.*.py"
@@ -219,3 +193,20 @@ check-quote-consistency = "yes"
219
193
 
220
194
  [tool.mypy]
221
195
  cache_dir = "local/.mypy_cache"
196
+
197
+ [dependency-groups]
198
+ lint = [
199
+ "codespell==2.4.3",
200
+ "pylint==4.1.2",
201
+ "ruff==0.16.10",
202
+ ]
203
+ test = [
204
+ "pytest==9.1.1",
205
+ "pytest-mock==3.16.0",
206
+ "pytest-sugar==1.1.1",
207
+ ]
208
+ tox = [
209
+ "tox==4.64.8",
210
+ "tox-uv==1.36.1",
211
+ ]
212
+ typing = ["mypy==2.4.0"]
@@ -0,0 +1,157 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.7.0,<0.14.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "cuid2"
7
+ version = "2.0.2"
8
+ authors = [
9
+ {name = "Will Gordon", email = "dev@gordoncode.dev"},
10
+ ]
11
+ description = "Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Security :: Cryptography",
27
+ "Typing :: Typed",
28
+ ]
29
+ keywords = ['crypt', 'security', 'uuid', 'guid', 'cuid', 'cryptography']
30
+
31
+ [project.urls]
32
+ repository = "https://github.com/gordon-code/cuid2/"
33
+
34
+ [project.scripts]
35
+ cuid2 = "cuid2.cli:main"
36
+
37
+ [tool.uv]
38
+ default-groups = ["lint", "test", "tox", "typing"]
39
+
40
+ [dependency-groups]
41
+ lint = [
42
+ "codespell==2.4.3",
43
+ "pylint==4.1.2",
44
+ "ruff==0.16.10",
45
+ ]
46
+ test = [
47
+ "pytest==9.1.1",
48
+ "pytest-mock==3.16.0",
49
+ "pytest-sugar==1.1.1",
50
+ ]
51
+ tox = [
52
+ "tox==4.64.8",
53
+ "tox-uv==1.36.1",
54
+ ]
55
+ typing = [
56
+ "mypy==2.4.0",
57
+ ]
58
+
59
+ [tool.tox]
60
+ legacy_tox_ini = """
61
+ [tox]
62
+ min_version = 4
63
+ env_list = py3{10,11,12,13,14}, check
64
+ work_dir = local/.tox
65
+ isolated_build = True
66
+
67
+ [testenv]
68
+ runner = uv-venv-lock-runner
69
+ description = run unit tests
70
+ dependency_groups = test
71
+ commands = pytest local/tests
72
+
73
+ [testenv:check]
74
+ runner = uv-venv-lock-runner
75
+ description = run linters and typing
76
+ package = skip
77
+ dependency_groups =
78
+ lint
79
+ typing
80
+ test
81
+ commands =
82
+ ruff check src/ local/tests/
83
+ ruff format --check src/ local/tests/
84
+ codespell src/ local/tests/
85
+ pylint src/ local/tests/
86
+ mypy src/ local/tests/
87
+ """
88
+
89
+ [tool.ruff]
90
+ line-length = 120
91
+ src = ["src"]
92
+ target-version = "py310"
93
+ cache-dir = "local/.ruff_cache"
94
+ force-exclude = true
95
+
96
+ [tool.ruff.lint]
97
+ # https://docs.astral.sh/ruff/rules/
98
+ select = [
99
+ "F", "E", "W", "C90", "I", "N", "ASYNC",
100
+ "S", "BLE", "B", "A", "COM", "C4",
101
+ "DTZ", "T10", "EM", "FA", "ISC", "ICN",
102
+ "G", "INP", "PIE", "T20", "PT", "Q", "RSE",
103
+ "RET", "SLF", "SLOT", "SIM", "INT", "ARG",
104
+ "PTH", "PGH", "PL", "TRY", "FLY", "PERF",
105
+ "LOG", "RUF",
106
+ ] # purposely not including "DJ", "YTT", "EXE", "PD", "NPY", "FBT" (boolean checking)
107
+ ignore = [
108
+ "D100", # Missing docstring in public module
109
+ "D101", # Missing docstring in public class
110
+ "D102", # Missing docstring in public method
111
+ "D104", # Missing docstring in public package
112
+ "D205", # Blank line required between summary line and description
113
+ "D401", # First line should be in imperative mood
114
+ "COM812", "ISC001", # Conflict with formatter
115
+ ]
116
+ # Don't automatically remove `print`
117
+ # Stop automatically removing unused imports
118
+ unfixable = ["T201", "F401", "F841"]
119
+
120
+ [tool.ruff.lint.per-file-ignores]
121
+ # Ignore `assert` in test files (S101), magic values (PLR2004), and private member accessed (SLF001)
122
+ "test_*.py" = ["S101", "PLR2004", "SLF001"]
123
+
124
+ [tool.ruff.lint.isort]
125
+ known-first-party = ["src"]
126
+ section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
127
+
128
+ [tool.ruff.lint.pydocstyle]
129
+ convention = "numpy"
130
+
131
+ [tool.pytest.ini_options]
132
+ minversion = "8.1"
133
+ cache_dir = "local/.pytest_cache"
134
+ python_files = "test_*.py"
135
+ testpaths = ["local/tests"]
136
+
137
+ [tool.pylint.master]
138
+ ignore-patterns = "test_.*.py"
139
+
140
+ [tool.pylint.format]
141
+ max-line-length = 120
142
+
143
+ [tool.pylint.messages_control]
144
+ disable = [
145
+ "missing-module-docstring",
146
+ "missing-class-docstring",
147
+ ]
148
+
149
+ [tool.pylint.design]
150
+ max-parents = 13
151
+ max-args = 10
152
+
153
+ [tool.pylint.string]
154
+ check-quote-consistency = "yes"
155
+
156
+ [tool.mypy]
157
+ cache_dir = "local/.mypy_cache"
@@ -2,4 +2,4 @@
2
2
 
3
3
  from .generator import DEFAULT_LENGTH, INITIAL_COUNT_MAX, Cuid, cuid_wrapper
4
4
 
5
- __all__ = ["Cuid", "DEFAULT_LENGTH", "INITIAL_COUNT_MAX", "cuid_wrapper"]
5
+ __all__ = ["DEFAULT_LENGTH", "INITIAL_COUNT_MAX", "Cuid", "cuid_wrapper"]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from argparse import ArgumentParser
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from cuid2.generator import DEFAULT_LENGTH, cuid_wrapper
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Sequence
10
+
11
+
12
+ def main(args: Optional[Sequence[str]] = None) -> None:
13
+ """Print out a CUID generated string. Used by the CLI console script."""
14
+ arguments_parser = ArgumentParser()
15
+ arguments_parser.add_argument(
16
+ nargs="?",
17
+ dest="length",
18
+ help="Length of generated cuid2",
19
+ type=int,
20
+ default=DEFAULT_LENGTH,
21
+ )
22
+ cli_arguments = arguments_parser.parse_args(args)
23
+
24
+ print(cuid_wrapper(cli_arguments.length)()) # noqa: T201 (print statement)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  import time
4
5
  from math import floor
5
6
  from secrets import SystemRandom
@@ -19,6 +20,7 @@ if TYPE_CHECKING:
19
20
  INITIAL_COUNT_MAX: Final[int] = 476782367
20
21
  DEFAULT_LENGTH: Final = 24
21
22
  MAXIMUM_LENGTH: Final = 98
23
+ _CUID_REGEX: Final[re.Pattern[str]] = re.compile(r"^[a-z][0-9a-z]+\Z")
22
24
 
23
25
 
24
26
  class Cuid: # pylint: disable=too-few-public-methods
@@ -40,18 +42,21 @@ class Cuid: # pylint: disable=too-few-public-methods
40
42
  counter : Callable[[int], Callable[[], int]], default=utils.create_counter
41
43
  The `counter` parameter is a callable that creates a counter returning an incremented value each time it
42
44
  is called. The `create_counter` function from the `utils` module is used by default.
43
- length : int, default=DEFAULT_LENGTH (4)
45
+ length : int, default=DEFAULT_LENGTH (24)
44
46
  The length parameter is an integer that determines the maximum length of the generated string.
45
- It has a default value of DEFAULT_LENGTH (4).
46
- A length value greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
47
+ It has a default value of DEFAULT_LENGTH (24).
48
+ A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
47
49
  fingerprint : "FingerprintCallable", default=utils.create_fingerprint
48
50
  The "fingerprint" parameter is a callable function that generates a unique identifier.
49
51
 
50
52
  Raises
51
53
  ------
52
54
  ValueError
53
- If the length parameter is greater than `MAXIMUM_LENGTH` (98 characters).
55
+ If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
54
56
  """
57
+ if isinstance(length, bool) or length < 1:
58
+ msg = "Length must be at least 1 character."
59
+ raise ValueError(msg)
55
60
  if length > MAXIMUM_LENGTH:
56
61
  msg = "Length must never exceed 98 characters."
57
62
  raise ValueError(msg)
@@ -69,7 +74,7 @@ class Cuid: # pylint: disable=too-few-public-methods
69
74
  length : int, optional
70
75
  The length parameter is an optional integer value that specifies the length of the generated string.
71
76
  If it is not provided, the default length value provided during class initialization is used.
72
- A length value greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
77
+ A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
73
78
 
74
79
  Returns
75
80
  -------
@@ -81,10 +86,13 @@ class Cuid: # pylint: disable=too-few-public-methods
81
86
  Raises
82
87
  ------
83
88
  ValueError
84
- If the length parameter is greater than `MAXIMUM_LENGTH` (98 characters).
89
+ If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
85
90
  """
86
- length = length or self._length
87
- if length > MAXIMUM_LENGTH:
91
+ 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:
88
96
  msg = "Length must never exceed 98 characters."
89
97
  raise ValueError(msg)
90
98
 
@@ -93,21 +101,62 @@ class Cuid: # pylint: disable=too-few-public-methods
93
101
  base36_time: str = utils.base36_encode(time.time_ns())
94
102
  base36_count: str = utils.base36_encode(self._counter())
95
103
 
96
- salt: str = utils.create_entropy(length=length, random_generator=self._random)
104
+ salt: str = utils.create_entropy(length=resolved_length, random_generator=self._random)
97
105
  hash_input: str = base36_time + salt + base36_count + self._fingerprint
98
106
 
99
- return first_letter + utils.create_hash(hash_input)[1 : length or self._length]
107
+ return first_letter + utils.create_hash(hash_input)[1:resolved_length]
100
108
 
109
+ @staticmethod
110
+ def is_cuid(cuid_str: str, min_length: int = 2, max_length: int = MAXIMUM_LENGTH) -> bool:
111
+ """Validates whether a given string is a valid CUID2 identifier.
101
112
 
102
- def cuid_wrapper() -> Callable[[], str]:
113
+ Parameters
114
+ ----------
115
+ cuid_str : str
116
+ The string to validate as a CUID2 identifier.
117
+ min_length : int, default=2
118
+ The minimum allowed length of the identifier. Values below 2 have no effect, since a valid
119
+ identifier always has a leading letter followed by at least one more character.
120
+ max_length : int, default=MAXIMUM_LENGTH (98)
121
+ The maximum allowed length of the identifier. Values above `MAXIMUM_LENGTH` are clamped to it.
122
+
123
+ Returns
124
+ -------
125
+ bool
126
+ True if `cuid_str` is a string starting with a lowercase ASCII letter followed by
127
+ lowercase base36 characters and has length between `min_length` and `max_length`
128
+ inclusive; False otherwise. The length bounds can only narrow the range of valid
129
+ lengths (2 to `MAXIMUM_LENGTH`), never widen it.
130
+ """
131
+ max_length = min(max_length, MAXIMUM_LENGTH)
132
+ return (
133
+ isinstance(cuid_str, str)
134
+ and min_length <= len(cuid_str) <= max_length
135
+ and bool(_CUID_REGEX.match(cuid_str))
136
+ )
137
+
138
+
139
+ def cuid_wrapper(length: int = DEFAULT_LENGTH) -> Callable[[], str]:
103
140
  """Wrap a single Cuid class instance and return a callable that generates a CUID string.
104
141
 
142
+ Parameters
143
+ ----------
144
+ length : int, default=DEFAULT_LENGTH (24)
145
+ The length parameter is an integer that determines the maximum length of the generated string.
146
+ It has a default value of DEFAULT_LENGTH (24).
147
+ A length value less than 1 or greater than `MAXIMUM_LENGTH` (98 characters) will raise a ValueError.
148
+
105
149
  Returns
106
150
  -------
107
151
  Callable[[], str]
108
152
  A callable that generates a CUID string.
153
+
154
+ Raises
155
+ ------
156
+ ValueError
157
+ If the length parameter is less than 1 or greater than `MAXIMUM_LENGTH` (98 characters).
109
158
  """
110
- cuid_generator: Cuid = Cuid()
159
+ cuid_generator: Cuid = Cuid(length=length)
111
160
 
112
161
  def cuid() -> str:
113
162
  return cuid_generator.generate()
@@ -19,9 +19,8 @@ BIG_LENGTH: Final = 32
19
19
 
20
20
 
21
21
  if TYPE_CHECKING:
22
- from hashlib import _Hash
23
-
24
22
  from _random import Random
23
+ from hashlib import _Hash
25
24
 
26
25
 
27
26
  def create_counter(count: int) -> Callable[[], int]:
@@ -1,8 +0,0 @@
1
- from cuid2.generator import cuid_wrapper
2
-
3
- generate_cuid = cuid_wrapper()
4
-
5
-
6
- def main() -> None:
7
- """Print out a CUID generated string. Used by the CLI console script."""
8
- print(generate_cuid()) # noqa: T201 (print statement)
File without changes
File without changes