cuid2 2.0.4__tar.gz → 2.2.0__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.
- cuid2-2.2.0/PKG-INFO +158 -0
- cuid2-2.2.0/README.md +130 -0
- cuid2-2.2.0/pyproject.toml +199 -0
- cuid2-2.2.0/pyproject.toml.orig +163 -0
- {cuid2-2.0.4 → cuid2-2.2.0}/src/cuid2/__init__.py +4 -9
- cuid2-2.2.0/src/cuid2/__main__.py +7 -0
- cuid2-2.2.0/src/cuid2/cli.py +270 -0
- cuid2-2.2.0/src/cuid2/generator.py +368 -0
- {cuid2-2.0.4 → cuid2-2.2.0}/src/cuid2/utils.py +21 -17
- cuid2-2.0.4/PKG-INFO +0 -102
- cuid2-2.0.4/README.md +0 -78
- cuid2-2.0.4/pyproject.toml +0 -212
- cuid2-2.0.4/pyproject.toml.orig +0 -157
- cuid2-2.0.4/src/cuid2/cli.py +0 -24
- cuid2-2.0.4/src/cuid2/generator.py +0 -209
- {cuid2-2.0.4 → cuid2-2.2.0}/LICENSE +0 -0
- {cuid2-2.0.4 → cuid2-2.2.0}/src/cuid2/py.typed +0 -0
cuid2-2.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cuid2
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance.
|
|
5
|
+
Keywords: crypt,security,uuid,guid,cuid,cryptography
|
|
6
|
+
Author: Will Gordon
|
|
7
|
+
Author-email: Will Gordon <dev@gordoncode.dev>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
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
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: repository, https://github.com/gordon-code/cuid2/
|
|
25
|
+
Project-URL: Changelog, https://github.com/gordon-code/cuid2/blob/main/CHANGELOG.md
|
|
26
|
+
Project-URL: Issues, https://github.com/gordon-code/cuid2/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# cuid2
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/cuid2/)
|
|
32
|
+
[](https://pypi.org/project/cuid2/)
|
|
33
|
+
[](https://pypi.org/project/cuid2/)
|
|
34
|
+
[](https://scorecard.dev/viewer/?uri=github.com/gordon-code/cuid2)
|
|
35
|
+
[](https://www.bestpractices.dev/projects/16036)
|
|
36
|
+
|
|
37
|
+
CUID2 for Python 3. Next generation GUIDs. Collision-resistant ids optimized for
|
|
38
|
+
horizontal scaling and performance.
|
|
39
|
+
|
|
40
|
+
A port of the [CUID2 reference implementation](https://github.com/paralleldrive/cuid2)
|
|
41
|
+
by [Parallel Drive](https://github.com/paralleldrive) to Python 3.
|
|
42
|
+
|
|
43
|
+
> :memo: Note: Originally taken from <https://github.com/overflowdigital> before it
|
|
44
|
+
> was unpublished. Thank you to @joshuathompsonlindley for your original contribution!
|
|
45
|
+
|
|
46
|
+
## What is CUID2?
|
|
47
|
+
|
|
48
|
+
* Secure: It's not feasible to guess the next ID (unguessability derives from the `secrets.SystemRandom` CSPRNG salt; per [RFC 9562 §8](https://www.rfc-editor.org/rfc/rfc9562.html#section-8), IDs are identifiers, not capability tokens or secrets).
|
|
49
|
+
* Collision resistant: It's extremely unlikely to generate the same ID twice.
|
|
50
|
+
* Horizontally scalable: Generate IDs on multiple machines without coordination.
|
|
51
|
+
* Offline-compatible: Generate IDs without a network connection.
|
|
52
|
+
* URL and name-friendly: No special characters.
|
|
53
|
+
|
|
54
|
+
## Why?
|
|
55
|
+
|
|
56
|
+
For more information on the theory and usage of CUID2, see the
|
|
57
|
+
[following](https://github.com/paralleldrive/cuid2#why).
|
|
58
|
+
|
|
59
|
+
## Improvements Over CUID
|
|
60
|
+
|
|
61
|
+
For more information on the improvements of CUID2 over CUID, see the
|
|
62
|
+
[following](https://github.com/paralleldrive/cuid2#improvements-over-cuid).
|
|
63
|
+
|
|
64
|
+
## When to Use CUID2
|
|
65
|
+
|
|
66
|
+
* **Zero Timestamp or Creation-Rate Leakage**: Time-ordered formats (`uuid.uuid7()` in Python 3.14, PostgreSQL 18 `uuidv7()`, ULID) embed a 48-bit millisecond timestamp in their prefix, allowing anyone with a public ID to read exact creation times and infer business volume or request velocity ([RFC 9562 §8](https://www.rfc-editor.org/rfc/rfc9562.html#section-8)). CUID2 hashes nanosecond time, CSPRNG salt, a monotonic counter, and a host fingerprint through **SHA3-512**, revealing nothing about when or where an ID was generated.
|
|
67
|
+
* **HTML, CSS, and Lexical Safety (`[a-z][0-9a-z]+`)**: Every CUID2 is guaranteed to start with a lowercase ASCII letter (`[a-z]`) and contain only `[0-9a-z]`. By contrast, time-ordered **UUIDv7** (`019...`) starts with `0` until the year 2527 (and `0–9` until year 7541), **ULID** always starts with a timestamp digit (`0–7`), **UUIDv4** starts with a numeric digit (`0–9`) 62.5% of the time (`10/16` hex values), and **NanoID** starts with `0–9`, `_`, or `-` 18.75% of the time (`12/64` alphabet chars). CUID2 IDs are always valid unescaped HTML `id` attributes and CSS selectors, and copy cleanly with a single double-click.
|
|
68
|
+
* **Collision Resistance & Entropy Math**:
|
|
69
|
+
* **123.6-Bit Default Keyspace (`length=24`)**: 1 leading lowercase letter ($26$) followed by 23 base36 characters ($36^{23}$) yields $26 \times 36^{23} \approx \mathbf{1.61 \times 10^{37}}$ possible IDs ($\approx \mathbf{123.6\text{ bits}}$ of entropy)—slightly more than UUIDv4's 122 random bits in a 33% shorter string (24 vs. 36 chars). Reaching a 50% birthday-bound collision probability requires $\sqrt{2 \ln(2) \times 26 \times 36^{23}} \approx \mathbf{4.7 \times 10^{18}}$ IDs (generating **149 million IDs/sec continuously for 1,000 years**).
|
|
70
|
+
* **Up to 165.0 Bits (`BIG_LENGTH = 32`)**: At `length=32`, the keyspace expands to $26 \times 36^{31} \approx \mathbf{4.54 \times 10^{49}}$ IDs ($\approx \mathbf{165.0\text{ bits}}$; 50% collision bound at $\approx 7.9 \times 10^{24}$ IDs).
|
|
71
|
+
* **Histogram De-Biasing**: Because $2^{512} \approx 1.34 \times 10^{154}$ is not an exact power of 36, the most-significant base36 digit of a 512-bit SHA3-512 digest is biased toward low digits; CUID2 drops that leading hash character (`[1:]`) and prepends an independent uniform `[a-z]` letter so every character position has a uniform distribution.
|
|
72
|
+
* **Thread, Pre-Fork, & Container Snapshot Resilience**: `Cuid`, `create_id`, `cuid_wrapper`, and `create_counter` are thread-safe (including on free-threaded Python, tested on `3.14t` and `3.15t`). Each generator records the process ID (`os.getpid()`) that built its state; the first ID generated in a forked child process (`multiprocessing`, Gunicorn, Celery prefork, and uWSGI workers, including uWSGI's default mode, which skips Python's `os.register_at_fork` hooks) rebuilds that state with a fresh lock, RNG state, random initial counter (`INITIAL_COUNT_MAX = 476,782,367`, giving $\approx 25,700$ concurrent hosts before a 50% chance of two hosts starting at the same counter value), and child-PID fingerprints (`os.getpid()`, `socket.gethostname()`, and environment variable keys). For cloned VM/container snapshots where no `fork()` syscall occurs, per-call `secrets.SystemRandom` (`os.urandom`) salt and `time.time_ns()` provide defense-in-depth against state collisions.
|
|
73
|
+
* **Polyglot ORM Compatibility (Prisma & Drizzle)**: Generates 24-character base36 primary keys compatible with TypeScript/Node.js services using Prisma (`@default(cuid(2))` / `@onCreate(cuid(2))`) or Drizzle (`@paralleldrive/cuid2`), and replaces the deprecated CUIDv1 (`cuid`) format.
|
|
74
|
+
|
|
75
|
+
| Property | CUID2 (`cuid2`) | UUIDv7 (`uuid.uuid7`) | UUIDv4 (`uuid.uuid4`) | ULID (`python-ulid`) | NanoID (`nanoid`) |
|
|
76
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
77
|
+
| **Default Length** | **24 chars** (`2..32`) | 36 chars | 36 chars | 26 chars | 21 chars |
|
|
78
|
+
| **Alphabet** | `[a-z0-9]` (base36) | `[0-9a-f-]` (hex) | `[0-9a-f-]` (hex) | Crockford Base32 | `[A-Za-z0-9_-]` (url64) |
|
|
79
|
+
| **Starts with Letter?** | **Yes (100% `[a-z]`)** | **No** (`0` until yr 2527) | Not guaranteed (37.5% `a–f`) | **No** (always `0–7`) | Not guaranteed (81.25%) |
|
|
80
|
+
| **Double-Click Copyable?** | **Yes** | No (hyphens) | No (hyphens) | **Yes** | No (`-` breaks word) |
|
|
81
|
+
| **Entropy / Random Bits** | **~123.6 bits** (`165.0` at 32) | 74 bits / ms | 122 bits | 80 bits / ms | 126 bits |
|
|
82
|
+
| **50% Collision Bound** | **~4.7 × 10¹⁸** (`~7.9 × 10²⁴` at 32) | ~1.6 × 10¹¹ / ms | ~2.7 × 10¹⁸ | ~1.3 × 10¹² / ms | ~1.1 × 10¹⁹ |
|
|
83
|
+
| **Hides Creation Timestamp?** | **Yes (SHA3-512)** | No (leaks 48-bit ms) | **Yes** | No (leaks 48-bit ms) | **Yes** |
|
|
84
|
+
| **Fork / Snapshot Safe?** | **Yes (per-PID state + CSPRNG)** | Counter can collide | OS CSPRNG only | Counter can collide | OS CSPRNG only |
|
|
85
|
+
| **B-Tree Index Locality** | Low (uniform hash) | **High (time-ordered)** | Low (uniform random) | **High (time-ordered)** | Low (uniform random) |
|
|
86
|
+
|
|
87
|
+
> **When to choose UUIDv7 instead:** Because CUID2 hashes its inputs via SHA3-512, IDs are uniformly distributed across the key space. For write-heavy relational tables where B-tree insert locality and 16-byte binary `UUID` storage matter more than hiding creation timestamps, prefer Python 3.14's `uuid.uuid7()` or PostgreSQL 18's `uuidv7()`.
|
|
88
|
+
|
|
89
|
+
## Install
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install cuid2
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
For most use cases—including Django, SQLAlchemy, and Pydantic model defaults—use the top-level `create_id` and `is_cuid` functions directly:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from cuid2 import create_id, is_cuid
|
|
101
|
+
|
|
102
|
+
my_cuid: str = create_id()
|
|
103
|
+
short_cuid: str = create_id(length=10)
|
|
104
|
+
|
|
105
|
+
print(is_cuid(my_cuid)) # True
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> **Tip for Django & ORM defaults:** Pass `default=create_id` directly on model fields (e.g., `models.CharField(max_length=24, default=create_id)`). Because `create_id` is a top-level function backed by a lazy singleton generator, Django `makemigrations` can serialize it directly and `Cuid()` initialization is deferred until the first ID is generated in each worker process.
|
|
109
|
+
|
|
110
|
+
You can also generate and validate CUIDs directly in the terminal:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cuid2 # Generate a 24-character CUID2
|
|
114
|
+
cuid2 -l 10 # Generate a 10-character CUID2 (or positional: cuid2 10)
|
|
115
|
+
cuid2 --slug # Generate a 5-character short ID slug
|
|
116
|
+
cuid2 -n 5 # Generate 5 CUID2s in a batch
|
|
117
|
+
cuid2 -f server-1 # Use "server-1" verbatim as the fingerprint (IDs stay random)
|
|
118
|
+
cuid2 --check <id> # Validate an ID (prints "<id>: valid" or "<id>: invalid"; -q for exit code only)
|
|
119
|
+
cuid2 -n 5 | cuid2 --check - # Validate newline-delimited IDs from stdin (blank lines are skipped)
|
|
120
|
+
python -m cuid2 # Run via module execution
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If a framework, dataclass, or Django migration requires a zero-argument callable with a custom length, use `cuid_wrapper` (which returns `functools.partial(create_id, length=10)` directly, making it picklable for `multiprocessing` and serializable by Django `makemigrations`):
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from collections.abc import Callable
|
|
127
|
+
|
|
128
|
+
from cuid2 import cuid_wrapper
|
|
129
|
+
|
|
130
|
+
short_cuid_generator: Callable[[], str] = cuid_wrapper(length=10)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def main() -> None:
|
|
134
|
+
short_cuid: str = short_cuid_generator()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Finally, for more explicit control of the CUID generator, you can instantiate the class directly:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from cuid2 import Cuid
|
|
141
|
+
|
|
142
|
+
CUID_GENERATOR: Cuid = Cuid(length=10)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> None:
|
|
146
|
+
my_cuid: str = CUID_GENERATOR.generate()
|
|
147
|
+
next_cuid: str = CUID_GENERATOR.generate()
|
|
148
|
+
|
|
149
|
+
# Validate CUID
|
|
150
|
+
print(Cuid.is_cuid(my_cuid)) # True
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`is_cuid` (and `Cuid.is_cuid`) also accepts optional `min_length` and `max_length` arguments to tighten the allowed length.
|
|
154
|
+
The bounds are clamped to 2 and 98 characters, so they can narrow but never widen what is considered valid.
|
|
155
|
+
|
|
156
|
+
## Changelog
|
|
157
|
+
|
|
158
|
+
See [CHANGELOG.md](https://github.com/gordon-code/cuid2/blob/main/CHANGELOG.md) for release history.
|
cuid2-2.2.0/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# cuid2
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/cuid2/)
|
|
4
|
+
[](https://pypi.org/project/cuid2/)
|
|
5
|
+
[](https://pypi.org/project/cuid2/)
|
|
6
|
+
[](https://scorecard.dev/viewer/?uri=github.com/gordon-code/cuid2)
|
|
7
|
+
[](https://www.bestpractices.dev/projects/16036)
|
|
8
|
+
|
|
9
|
+
CUID2 for Python 3. Next generation GUIDs. Collision-resistant ids optimized for
|
|
10
|
+
horizontal scaling and performance.
|
|
11
|
+
|
|
12
|
+
A port of the [CUID2 reference implementation](https://github.com/paralleldrive/cuid2)
|
|
13
|
+
by [Parallel Drive](https://github.com/paralleldrive) to Python 3.
|
|
14
|
+
|
|
15
|
+
> :memo: Note: Originally taken from <https://github.com/overflowdigital> before it
|
|
16
|
+
> was unpublished. Thank you to @joshuathompsonlindley for your original contribution!
|
|
17
|
+
|
|
18
|
+
## What is CUID2?
|
|
19
|
+
|
|
20
|
+
* Secure: It's not feasible to guess the next ID (unguessability derives from the `secrets.SystemRandom` CSPRNG salt; per [RFC 9562 §8](https://www.rfc-editor.org/rfc/rfc9562.html#section-8), IDs are identifiers, not capability tokens or secrets).
|
|
21
|
+
* Collision resistant: It's extremely unlikely to generate the same ID twice.
|
|
22
|
+
* Horizontally scalable: Generate IDs on multiple machines without coordination.
|
|
23
|
+
* Offline-compatible: Generate IDs without a network connection.
|
|
24
|
+
* URL and name-friendly: No special characters.
|
|
25
|
+
|
|
26
|
+
## Why?
|
|
27
|
+
|
|
28
|
+
For more information on the theory and usage of CUID2, see the
|
|
29
|
+
[following](https://github.com/paralleldrive/cuid2#why).
|
|
30
|
+
|
|
31
|
+
## Improvements Over CUID
|
|
32
|
+
|
|
33
|
+
For more information on the improvements of CUID2 over CUID, see the
|
|
34
|
+
[following](https://github.com/paralleldrive/cuid2#improvements-over-cuid).
|
|
35
|
+
|
|
36
|
+
## When to Use CUID2
|
|
37
|
+
|
|
38
|
+
* **Zero Timestamp or Creation-Rate Leakage**: Time-ordered formats (`uuid.uuid7()` in Python 3.14, PostgreSQL 18 `uuidv7()`, ULID) embed a 48-bit millisecond timestamp in their prefix, allowing anyone with a public ID to read exact creation times and infer business volume or request velocity ([RFC 9562 §8](https://www.rfc-editor.org/rfc/rfc9562.html#section-8)). CUID2 hashes nanosecond time, CSPRNG salt, a monotonic counter, and a host fingerprint through **SHA3-512**, revealing nothing about when or where an ID was generated.
|
|
39
|
+
* **HTML, CSS, and Lexical Safety (`[a-z][0-9a-z]+`)**: Every CUID2 is guaranteed to start with a lowercase ASCII letter (`[a-z]`) and contain only `[0-9a-z]`. By contrast, time-ordered **UUIDv7** (`019...`) starts with `0` until the year 2527 (and `0–9` until year 7541), **ULID** always starts with a timestamp digit (`0–7`), **UUIDv4** starts with a numeric digit (`0–9`) 62.5% of the time (`10/16` hex values), and **NanoID** starts with `0–9`, `_`, or `-` 18.75% of the time (`12/64` alphabet chars). CUID2 IDs are always valid unescaped HTML `id` attributes and CSS selectors, and copy cleanly with a single double-click.
|
|
40
|
+
* **Collision Resistance & Entropy Math**:
|
|
41
|
+
* **123.6-Bit Default Keyspace (`length=24`)**: 1 leading lowercase letter ($26$) followed by 23 base36 characters ($36^{23}$) yields $26 \times 36^{23} \approx \mathbf{1.61 \times 10^{37}}$ possible IDs ($\approx \mathbf{123.6\text{ bits}}$ of entropy)—slightly more than UUIDv4's 122 random bits in a 33% shorter string (24 vs. 36 chars). Reaching a 50% birthday-bound collision probability requires $\sqrt{2 \ln(2) \times 26 \times 36^{23}} \approx \mathbf{4.7 \times 10^{18}}$ IDs (generating **149 million IDs/sec continuously for 1,000 years**).
|
|
42
|
+
* **Up to 165.0 Bits (`BIG_LENGTH = 32`)**: At `length=32`, the keyspace expands to $26 \times 36^{31} \approx \mathbf{4.54 \times 10^{49}}$ IDs ($\approx \mathbf{165.0\text{ bits}}$; 50% collision bound at $\approx 7.9 \times 10^{24}$ IDs).
|
|
43
|
+
* **Histogram De-Biasing**: Because $2^{512} \approx 1.34 \times 10^{154}$ is not an exact power of 36, the most-significant base36 digit of a 512-bit SHA3-512 digest is biased toward low digits; CUID2 drops that leading hash character (`[1:]`) and prepends an independent uniform `[a-z]` letter so every character position has a uniform distribution.
|
|
44
|
+
* **Thread, Pre-Fork, & Container Snapshot Resilience**: `Cuid`, `create_id`, `cuid_wrapper`, and `create_counter` are thread-safe (including on free-threaded Python, tested on `3.14t` and `3.15t`). Each generator records the process ID (`os.getpid()`) that built its state; the first ID generated in a forked child process (`multiprocessing`, Gunicorn, Celery prefork, and uWSGI workers, including uWSGI's default mode, which skips Python's `os.register_at_fork` hooks) rebuilds that state with a fresh lock, RNG state, random initial counter (`INITIAL_COUNT_MAX = 476,782,367`, giving $\approx 25,700$ concurrent hosts before a 50% chance of two hosts starting at the same counter value), and child-PID fingerprints (`os.getpid()`, `socket.gethostname()`, and environment variable keys). For cloned VM/container snapshots where no `fork()` syscall occurs, per-call `secrets.SystemRandom` (`os.urandom`) salt and `time.time_ns()` provide defense-in-depth against state collisions.
|
|
45
|
+
* **Polyglot ORM Compatibility (Prisma & Drizzle)**: Generates 24-character base36 primary keys compatible with TypeScript/Node.js services using Prisma (`@default(cuid(2))` / `@onCreate(cuid(2))`) or Drizzle (`@paralleldrive/cuid2`), and replaces the deprecated CUIDv1 (`cuid`) format.
|
|
46
|
+
|
|
47
|
+
| Property | CUID2 (`cuid2`) | UUIDv7 (`uuid.uuid7`) | UUIDv4 (`uuid.uuid4`) | ULID (`python-ulid`) | NanoID (`nanoid`) |
|
|
48
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
49
|
+
| **Default Length** | **24 chars** (`2..32`) | 36 chars | 36 chars | 26 chars | 21 chars |
|
|
50
|
+
| **Alphabet** | `[a-z0-9]` (base36) | `[0-9a-f-]` (hex) | `[0-9a-f-]` (hex) | Crockford Base32 | `[A-Za-z0-9_-]` (url64) |
|
|
51
|
+
| **Starts with Letter?** | **Yes (100% `[a-z]`)** | **No** (`0` until yr 2527) | Not guaranteed (37.5% `a–f`) | **No** (always `0–7`) | Not guaranteed (81.25%) |
|
|
52
|
+
| **Double-Click Copyable?** | **Yes** | No (hyphens) | No (hyphens) | **Yes** | No (`-` breaks word) |
|
|
53
|
+
| **Entropy / Random Bits** | **~123.6 bits** (`165.0` at 32) | 74 bits / ms | 122 bits | 80 bits / ms | 126 bits |
|
|
54
|
+
| **50% Collision Bound** | **~4.7 × 10¹⁸** (`~7.9 × 10²⁴` at 32) | ~1.6 × 10¹¹ / ms | ~2.7 × 10¹⁸ | ~1.3 × 10¹² / ms | ~1.1 × 10¹⁹ |
|
|
55
|
+
| **Hides Creation Timestamp?** | **Yes (SHA3-512)** | No (leaks 48-bit ms) | **Yes** | No (leaks 48-bit ms) | **Yes** |
|
|
56
|
+
| **Fork / Snapshot Safe?** | **Yes (per-PID state + CSPRNG)** | Counter can collide | OS CSPRNG only | Counter can collide | OS CSPRNG only |
|
|
57
|
+
| **B-Tree Index Locality** | Low (uniform hash) | **High (time-ordered)** | Low (uniform random) | **High (time-ordered)** | Low (uniform random) |
|
|
58
|
+
|
|
59
|
+
> **When to choose UUIDv7 instead:** Because CUID2 hashes its inputs via SHA3-512, IDs are uniformly distributed across the key space. For write-heavy relational tables where B-tree insert locality and 16-byte binary `UUID` storage matter more than hiding creation timestamps, prefer Python 3.14's `uuid.uuid7()` or PostgreSQL 18's `uuidv7()`.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install cuid2
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
For most use cases—including Django, SQLAlchemy, and Pydantic model defaults—use the top-level `create_id` and `is_cuid` functions directly:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from cuid2 import create_id, is_cuid
|
|
73
|
+
|
|
74
|
+
my_cuid: str = create_id()
|
|
75
|
+
short_cuid: str = create_id(length=10)
|
|
76
|
+
|
|
77
|
+
print(is_cuid(my_cuid)) # True
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> **Tip for Django & ORM defaults:** Pass `default=create_id` directly on model fields (e.g., `models.CharField(max_length=24, default=create_id)`). Because `create_id` is a top-level function backed by a lazy singleton generator, Django `makemigrations` can serialize it directly and `Cuid()` initialization is deferred until the first ID is generated in each worker process.
|
|
81
|
+
|
|
82
|
+
You can also generate and validate CUIDs directly in the terminal:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cuid2 # Generate a 24-character CUID2
|
|
86
|
+
cuid2 -l 10 # Generate a 10-character CUID2 (or positional: cuid2 10)
|
|
87
|
+
cuid2 --slug # Generate a 5-character short ID slug
|
|
88
|
+
cuid2 -n 5 # Generate 5 CUID2s in a batch
|
|
89
|
+
cuid2 -f server-1 # Use "server-1" verbatim as the fingerprint (IDs stay random)
|
|
90
|
+
cuid2 --check <id> # Validate an ID (prints "<id>: valid" or "<id>: invalid"; -q for exit code only)
|
|
91
|
+
cuid2 -n 5 | cuid2 --check - # Validate newline-delimited IDs from stdin (blank lines are skipped)
|
|
92
|
+
python -m cuid2 # Run via module execution
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If a framework, dataclass, or Django migration requires a zero-argument callable with a custom length, use `cuid_wrapper` (which returns `functools.partial(create_id, length=10)` directly, making it picklable for `multiprocessing` and serializable by Django `makemigrations`):
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from collections.abc import Callable
|
|
99
|
+
|
|
100
|
+
from cuid2 import cuid_wrapper
|
|
101
|
+
|
|
102
|
+
short_cuid_generator: Callable[[], str] = cuid_wrapper(length=10)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main() -> None:
|
|
106
|
+
short_cuid: str = short_cuid_generator()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Finally, for more explicit control of the CUID generator, you can instantiate the class directly:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from cuid2 import Cuid
|
|
113
|
+
|
|
114
|
+
CUID_GENERATOR: Cuid = Cuid(length=10)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> None:
|
|
118
|
+
my_cuid: str = CUID_GENERATOR.generate()
|
|
119
|
+
next_cuid: str = CUID_GENERATOR.generate()
|
|
120
|
+
|
|
121
|
+
# Validate CUID
|
|
122
|
+
print(Cuid.is_cuid(my_cuid)) # True
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`is_cuid` (and `Cuid.is_cuid`) also accepts optional `min_length` and `max_length` arguments to tighten the allowed length.
|
|
126
|
+
The bounds are clamped to 2 and 98 characters, so they can narrow but never widen what is considered valid.
|
|
127
|
+
|
|
128
|
+
## Changelog
|
|
129
|
+
|
|
130
|
+
See [CHANGELOG.md](https://github.com/gordon-code/cuid2/blob/main/CHANGELOG.md) for release history.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build==0.13.5"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cuid2"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
description = "Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
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
|
+
"Programming Language :: Python :: 3.15",
|
|
24
|
+
"Programming Language :: Python :: Free Threading :: 3 - Stable",
|
|
25
|
+
"Topic :: Security :: Cryptography",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
keywords = [
|
|
29
|
+
"crypt",
|
|
30
|
+
"security",
|
|
31
|
+
"uuid",
|
|
32
|
+
"guid",
|
|
33
|
+
"cuid",
|
|
34
|
+
"cryptography",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[[project.authors]]
|
|
38
|
+
name = "Will Gordon"
|
|
39
|
+
email = "dev@gordoncode.dev"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
repository = "https://github.com/gordon-code/cuid2/"
|
|
43
|
+
Changelog = "https://github.com/gordon-code/cuid2/blob/main/CHANGELOG.md"
|
|
44
|
+
Issues = "https://github.com/gordon-code/cuid2/issues"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
cuid2 = "cuid2.cli:main"
|
|
48
|
+
|
|
49
|
+
[tool.uv]
|
|
50
|
+
default-groups = [
|
|
51
|
+
"build",
|
|
52
|
+
"lint",
|
|
53
|
+
"test",
|
|
54
|
+
"typing",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.codespell]
|
|
58
|
+
check-hidden = ""
|
|
59
|
+
ignore-words-list = "intoto"
|
|
60
|
+
skip = ".cache,.claude,.coverage,.coverage.*,.git,.hypothesis,.mypy_cache,.pdm-build,.pytest_cache,.ruff_cache,.rumdl_cache,.venv,__pycache__,build,dist,hack,uv.lock"
|
|
61
|
+
|
|
62
|
+
[tool.rumdl]
|
|
63
|
+
cache-dir = "local/.rumdl_cache"
|
|
64
|
+
disable = ["MD013"]
|
|
65
|
+
exclude = ["hack"]
|
|
66
|
+
|
|
67
|
+
[tool.rumdl.per-file-ignores]
|
|
68
|
+
".github/**/*.md" = ["MD041"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff]
|
|
71
|
+
line-length = 120
|
|
72
|
+
src = ["src"]
|
|
73
|
+
target-version = "py310"
|
|
74
|
+
cache-dir = "local/.ruff_cache"
|
|
75
|
+
extend-exclude = ["hack"]
|
|
76
|
+
force-exclude = true
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
select = ["ALL"]
|
|
80
|
+
ignore = [
|
|
81
|
+
"COM812",
|
|
82
|
+
"ISC001",
|
|
83
|
+
"CPY001",
|
|
84
|
+
]
|
|
85
|
+
unfixable = [
|
|
86
|
+
"T201",
|
|
87
|
+
"F401",
|
|
88
|
+
"F841",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.per-file-ignores]
|
|
92
|
+
"local/tests/*.py" = [
|
|
93
|
+
"D100",
|
|
94
|
+
"D101",
|
|
95
|
+
"D102",
|
|
96
|
+
"D103",
|
|
97
|
+
"D104",
|
|
98
|
+
"D205",
|
|
99
|
+
"D401",
|
|
100
|
+
"FBT001",
|
|
101
|
+
"PLR2004",
|
|
102
|
+
"S101",
|
|
103
|
+
"SLF001",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.isort]
|
|
107
|
+
section-order = [
|
|
108
|
+
"future",
|
|
109
|
+
"standard-library",
|
|
110
|
+
"third-party",
|
|
111
|
+
"first-party",
|
|
112
|
+
"local-folder",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
[tool.ruff.lint.pydocstyle]
|
|
116
|
+
convention = "numpy"
|
|
117
|
+
|
|
118
|
+
[tool.pytest.ini_options]
|
|
119
|
+
minversion = "9.0"
|
|
120
|
+
strict = true
|
|
121
|
+
cache_dir = "local/.pytest_cache"
|
|
122
|
+
python_files = "test_*.py"
|
|
123
|
+
pythonpath = ["."]
|
|
124
|
+
testpaths = ["local/tests"]
|
|
125
|
+
addopts = [
|
|
126
|
+
"--import-mode=importlib",
|
|
127
|
+
"--cov",
|
|
128
|
+
"--cov-report=term-missing:skip-covered",
|
|
129
|
+
"-m",
|
|
130
|
+
"not slow and not heavy",
|
|
131
|
+
]
|
|
132
|
+
filterwarnings = ["error"]
|
|
133
|
+
markers = [
|
|
134
|
+
"slow: mark test as slow to run",
|
|
135
|
+
"heavy: mark collision test as heavy (>=10M IDs)",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[tool.coverage.run]
|
|
139
|
+
branch = true
|
|
140
|
+
source_pkgs = ["cuid2"]
|
|
141
|
+
|
|
142
|
+
[tool.coverage.paths]
|
|
143
|
+
source = [
|
|
144
|
+
"src/cuid2",
|
|
145
|
+
"*/site-packages/cuid2",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[tool.coverage.report]
|
|
149
|
+
fail_under = 100
|
|
150
|
+
show_missing = true
|
|
151
|
+
exclude_also = ["if TYPE_CHECKING:"]
|
|
152
|
+
|
|
153
|
+
[tool.mypy]
|
|
154
|
+
cache_dir = "local/.mypy_cache"
|
|
155
|
+
files = ["."]
|
|
156
|
+
exclude = [
|
|
157
|
+
"^build/",
|
|
158
|
+
"^dist/",
|
|
159
|
+
"^hack/",
|
|
160
|
+
]
|
|
161
|
+
strict = true
|
|
162
|
+
|
|
163
|
+
[[tool.mypy.overrides]]
|
|
164
|
+
module = [
|
|
165
|
+
"atheris",
|
|
166
|
+
"keepachangelog",
|
|
167
|
+
]
|
|
168
|
+
ignore_missing_imports = true
|
|
169
|
+
|
|
170
|
+
[tool.pyright]
|
|
171
|
+
exclude = [
|
|
172
|
+
"build",
|
|
173
|
+
"dist",
|
|
174
|
+
"hack",
|
|
175
|
+
]
|
|
176
|
+
strict = ["src"]
|
|
177
|
+
|
|
178
|
+
[dependency-groups]
|
|
179
|
+
build = ["twine==7.0.0"]
|
|
180
|
+
lint = [
|
|
181
|
+
"codespell==2.4.3",
|
|
182
|
+
"prek==0.5.7",
|
|
183
|
+
"ruff==0.17.0",
|
|
184
|
+
"rumdl==0.3.0",
|
|
185
|
+
"zizmor==1.31.1; python_version >= '3.11'",
|
|
186
|
+
]
|
|
187
|
+
test = [
|
|
188
|
+
"coverage[toml]==7.16.2",
|
|
189
|
+
"hypothesis==6.168.5; python_version < '3.11'",
|
|
190
|
+
"hypothesis==6.170.5; python_version >= '3.11'",
|
|
191
|
+
"keepachangelog==2.0.0",
|
|
192
|
+
"pytest==9.1.1",
|
|
193
|
+
"pytest-cov==7.1.0",
|
|
194
|
+
"pytest-mock==3.16.0",
|
|
195
|
+
]
|
|
196
|
+
typing = [
|
|
197
|
+
"mypy==2.4.0",
|
|
198
|
+
"pyright[nodejs]==1.1.414",
|
|
199
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build==0.13.5"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cuid2"
|
|
7
|
+
version = "2.2.0"
|
|
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
|
+
"Programming Language :: Python :: 3.15",
|
|
27
|
+
"Programming Language :: Python :: Free Threading :: 3 - Stable",
|
|
28
|
+
"Topic :: Security :: Cryptography",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
keywords = ['crypt', 'security', 'uuid', 'guid', 'cuid', 'cryptography']
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
repository = "https://github.com/gordon-code/cuid2/"
|
|
35
|
+
Changelog = "https://github.com/gordon-code/cuid2/blob/main/CHANGELOG.md"
|
|
36
|
+
Issues = "https://github.com/gordon-code/cuid2/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
cuid2 = "cuid2.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.uv]
|
|
42
|
+
default-groups = ["build", "lint", "test", "typing"]
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
build = [
|
|
46
|
+
"twine==7.0.0",
|
|
47
|
+
]
|
|
48
|
+
lint = [
|
|
49
|
+
"codespell==2.4.3",
|
|
50
|
+
"prek==0.5.7",
|
|
51
|
+
"ruff==0.17.0",
|
|
52
|
+
"rumdl==0.3.0",
|
|
53
|
+
"zizmor==1.31.1; python_version >= '3.11'",
|
|
54
|
+
]
|
|
55
|
+
test = [
|
|
56
|
+
"coverage[toml]==7.16.2",
|
|
57
|
+
"hypothesis==6.168.5; python_version < '3.11'",
|
|
58
|
+
"hypothesis==6.170.5; python_version >= '3.11'",
|
|
59
|
+
"keepachangelog==2.0.0",
|
|
60
|
+
"pytest==9.1.1",
|
|
61
|
+
"pytest-cov==7.1.0",
|
|
62
|
+
"pytest-mock==3.16.0",
|
|
63
|
+
]
|
|
64
|
+
typing = [
|
|
65
|
+
"mypy==2.4.0",
|
|
66
|
+
"pyright[nodejs]==1.1.414",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.codespell]
|
|
70
|
+
check-hidden = ""
|
|
71
|
+
ignore-words-list = "intoto"
|
|
72
|
+
skip = ".cache,.claude,.coverage,.coverage.*,.git,.hypothesis,.mypy_cache,.pdm-build,.pytest_cache,.ruff_cache,.rumdl_cache,.venv,__pycache__,build,dist,hack,uv.lock"
|
|
73
|
+
|
|
74
|
+
[tool.rumdl]
|
|
75
|
+
cache-dir = "local/.rumdl_cache"
|
|
76
|
+
disable = ["MD013"]
|
|
77
|
+
exclude = ["hack"]
|
|
78
|
+
|
|
79
|
+
[tool.rumdl.per-file-ignores]
|
|
80
|
+
".github/**/*.md" = ["MD041"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 120
|
|
84
|
+
src = ["src"]
|
|
85
|
+
target-version = "py310"
|
|
86
|
+
cache-dir = "local/.ruff_cache"
|
|
87
|
+
extend-exclude = ["hack"]
|
|
88
|
+
force-exclude = true
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint]
|
|
91
|
+
# https://docs.astral.sh/ruff/rules/
|
|
92
|
+
select = ["ALL"]
|
|
93
|
+
ignore = [
|
|
94
|
+
"COM812", "ISC001", # Conflict with formatter
|
|
95
|
+
"CPY001", # Missing copyright notice at top of file
|
|
96
|
+
]
|
|
97
|
+
# Don't automatically remove `print`
|
|
98
|
+
# Stop automatically removing unused imports
|
|
99
|
+
unfixable = ["T201", "F401", "F841"]
|
|
100
|
+
|
|
101
|
+
[tool.ruff.lint.per-file-ignores]
|
|
102
|
+
# Test files: allow assert (S101), magic values (PLR2004), private member access (SLF001),
|
|
103
|
+
# boolean test params (FBT001), and test docstring conventions (D1xx, D205, D401)
|
|
104
|
+
"local/tests/*.py" = ["D100", "D101", "D102", "D103", "D104", "D205", "D401", "FBT001", "PLR2004", "S101", "SLF001"]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.isort]
|
|
107
|
+
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint.pydocstyle]
|
|
110
|
+
convention = "numpy"
|
|
111
|
+
|
|
112
|
+
[tool.pytest.ini_options]
|
|
113
|
+
minversion = "9.0"
|
|
114
|
+
strict = true
|
|
115
|
+
cache_dir = "local/.pytest_cache"
|
|
116
|
+
python_files = "test_*.py"
|
|
117
|
+
pythonpath = ["."]
|
|
118
|
+
testpaths = ["local/tests"]
|
|
119
|
+
addopts = [
|
|
120
|
+
"--import-mode=importlib",
|
|
121
|
+
"--cov",
|
|
122
|
+
"--cov-report=term-missing:skip-covered",
|
|
123
|
+
"-m",
|
|
124
|
+
"not slow and not heavy",
|
|
125
|
+
]
|
|
126
|
+
filterwarnings = [
|
|
127
|
+
"error",
|
|
128
|
+
]
|
|
129
|
+
markers = [
|
|
130
|
+
"slow: mark test as slow to run",
|
|
131
|
+
"heavy: mark collision test as heavy (>=10M IDs)",
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
[tool.coverage.run]
|
|
135
|
+
branch = true
|
|
136
|
+
source_pkgs = ["cuid2"]
|
|
137
|
+
|
|
138
|
+
[tool.coverage.paths]
|
|
139
|
+
source = [
|
|
140
|
+
"src/cuid2",
|
|
141
|
+
"*/site-packages/cuid2",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
[tool.coverage.report]
|
|
145
|
+
fail_under = 100
|
|
146
|
+
show_missing = true
|
|
147
|
+
exclude_also = [
|
|
148
|
+
"if TYPE_CHECKING:",
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
[tool.mypy]
|
|
152
|
+
cache_dir = "local/.mypy_cache"
|
|
153
|
+
files = ["."]
|
|
154
|
+
exclude = ["^build/", "^dist/", "^hack/"]
|
|
155
|
+
strict = true
|
|
156
|
+
|
|
157
|
+
[[tool.mypy.overrides]]
|
|
158
|
+
module = ["atheris", "keepachangelog"]
|
|
159
|
+
ignore_missing_imports = true
|
|
160
|
+
|
|
161
|
+
[tool.pyright]
|
|
162
|
+
exclude = ["build", "dist", "hack"]
|
|
163
|
+
strict = ["src"]
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
"""Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance."""
|
|
2
2
|
|
|
3
|
-
from .generator import
|
|
4
|
-
|
|
5
|
-
DEFAULT_LENGTH,
|
|
6
|
-
INITIAL_COUNT_MAX,
|
|
7
|
-
MAXIMUM_LENGTH,
|
|
8
|
-
MIN_LENGTH,
|
|
9
|
-
Cuid,
|
|
10
|
-
cuid_wrapper,
|
|
11
|
-
)
|
|
3
|
+
from cuid2.generator import Cuid, create_id, cuid_wrapper, is_cuid
|
|
4
|
+
from cuid2.utils import BIG_LENGTH, DEFAULT_LENGTH, INITIAL_COUNT_MAX, MAXIMUM_LENGTH, MIN_LENGTH
|
|
12
5
|
|
|
13
6
|
__all__ = [
|
|
14
7
|
"BIG_LENGTH",
|
|
@@ -17,5 +10,7 @@ __all__ = [
|
|
|
17
10
|
"MAXIMUM_LENGTH",
|
|
18
11
|
"MIN_LENGTH",
|
|
19
12
|
"Cuid",
|
|
13
|
+
"create_id",
|
|
20
14
|
"cuid_wrapper",
|
|
15
|
+
"is_cuid",
|
|
21
16
|
]
|