python-ulid 3.2.1__py3-none-any.whl → 4.0.0__py3-none-any.whl
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.
- {python_ulid-3.2.1.dist-info → python_ulid-4.0.0.dist-info}/METADATA +59 -1
- python_ulid-4.0.0.dist-info/RECORD +10 -0
- ulid/__init__.py +207 -53
- python_ulid-3.2.1.dist-info/RECORD +0 -10
- {python_ulid-3.2.1.dist-info → python_ulid-4.0.0.dist-info}/WHEEL +0 -0
- {python_ulid-3.2.1.dist-info → python_ulid-4.0.0.dist-info}/entry_points.txt +0 -0
- {python_ulid-3.2.1.dist-info → python_ulid-4.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-ulid
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Universally unique lexicographically sortable identifier
|
|
5
5
|
Project-URL: Homepage, https://github.com/mdomke/python-ulid
|
|
6
6
|
Project-URL: Documentation, https://python-ulid.readthedocs.io
|
|
@@ -164,6 +164,64 @@ timestamp, then :math:`r_2 = r_1 + 1`.
|
|
|
164
164
|
|
|
165
165
|
.. monotonic-end
|
|
166
166
|
|
|
167
|
+
.. generators-begin
|
|
168
|
+
|
|
169
|
+
Generators and policies
|
|
170
|
+
-----------------------
|
|
171
|
+
|
|
172
|
+
Every ``ULID`` is produced by a ``ULIDGenerator``, which samples a clock for the timestamp,
|
|
173
|
+
sources entropy for the randomness, and enforces a *monotonicity policy*. The bare ``ULID()``
|
|
174
|
+
constructor and the ``ULID.from_*`` factory methods delegate to a shared module-level
|
|
175
|
+
``default_generator``.
|
|
176
|
+
|
|
177
|
+
For most use cases the default is all you need. To customize generation — a different clock, a
|
|
178
|
+
custom entropy source, or another monotonicity policy — create your own ``ULIDGenerator`` and
|
|
179
|
+
call ``generate()``
|
|
180
|
+
|
|
181
|
+
.. code-block:: pycon
|
|
182
|
+
|
|
183
|
+
>>> from ulid import ULIDGenerator
|
|
184
|
+
>>> generator = ULIDGenerator()
|
|
185
|
+
>>> generator.generate()
|
|
186
|
+
ULID(01HB0N8Q4RCE7YB1M2VZK9WX3T)
|
|
187
|
+
|
|
188
|
+
Choosing a monotonicity policy
|
|
189
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
190
|
+
|
|
191
|
+
A policy decides how the randomness is resolved when multiple ULIDs are generated within the same
|
|
192
|
+
millisecond. Three policies are available:
|
|
193
|
+
|
|
194
|
+
* ``StrictMonotonicPolicy`` *(default)* — increments the randomness by 1 on a same-millisecond
|
|
195
|
+
collision and raises ``ValueError`` if the randomness is exhausted. This is the behaviour
|
|
196
|
+
described under `Monotonic Support`_.
|
|
197
|
+
* ``LaxMonotonicPolicy`` — increments like the strict policy, but regenerates fresh randomness
|
|
198
|
+
instead of raising when the randomness is exhausted.
|
|
199
|
+
* ``PureRandomPolicy`` — ignores previous state and always draws fresh randomness, maximizing
|
|
200
|
+
entropy at the cost of same-millisecond sort order.
|
|
201
|
+
|
|
202
|
+
.. code-block:: pycon
|
|
203
|
+
|
|
204
|
+
>>> from ulid import ULIDGenerator, PureRandomPolicy
|
|
205
|
+
>>> generator = ULIDGenerator(policy=PureRandomPolicy())
|
|
206
|
+
>>> generator.generate()
|
|
207
|
+
ULID(01HB0N9F3TA5KDQ6ZE0WYV7MRC)
|
|
208
|
+
|
|
209
|
+
Overriding the default generator
|
|
210
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
211
|
+
|
|
212
|
+
To make ``ULID()`` and the ``ULID.from_*`` constructors use a custom generator globally, reassign
|
|
213
|
+
``ulid.default_generator``
|
|
214
|
+
|
|
215
|
+
.. code-block:: pycon
|
|
216
|
+
|
|
217
|
+
>>> import ulid
|
|
218
|
+
>>> from ulid import ULID, ULIDGenerator, LaxMonotonicPolicy
|
|
219
|
+
>>> ulid.default_generator = ULIDGenerator(policy=LaxMonotonicPolicy())
|
|
220
|
+
>>> ULID() # now generated with the lax policy
|
|
221
|
+
ULID(01HB0NB7X2M4C8VKQ0ZF5WD9RA)
|
|
222
|
+
|
|
223
|
+
.. generators-end
|
|
224
|
+
|
|
167
225
|
.. cli-begin
|
|
168
226
|
|
|
169
227
|
Command line interface
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
ulid/__init__.py,sha256=kn3vK2Evw0zzVnyO_2qNNYa8UQS0_VjVz30r65ZHJ0Y,21233
|
|
2
|
+
ulid/__main__.py,sha256=ZP8ZbhQ_o-O4Ckbba87bZ4NkwzrSFhAkrRLmUnzkW0Y,5586
|
|
3
|
+
ulid/base32.py,sha256=iUE5BGPW5Dul-Eu3ITvcymk8acNkIFAVTlrL12YIPKI,5961
|
|
4
|
+
ulid/constants.py,sha256=lbCLRNQXq8qnOdz_w-nJf_j5dC2X7Gjv-8GJUbs5jX8,503
|
|
5
|
+
ulid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
python_ulid-4.0.0.dist-info/METADATA,sha256=K2ql6NKIOsg2iB4LyUZXqyjItV9leSJFDcI3_aDsnXU,8758
|
|
7
|
+
python_ulid-4.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
python_ulid-4.0.0.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
|
|
9
|
+
python_ulid-4.0.0.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
|
|
10
|
+
python_ulid-4.0.0.dist-info/RECORD,,
|
ulid/__init__.py
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import abc
|
|
3
4
|
import functools
|
|
4
5
|
import os
|
|
5
6
|
import time
|
|
6
7
|
import uuid
|
|
8
|
+
from collections.abc import Callable
|
|
7
9
|
from datetime import datetime
|
|
8
10
|
from datetime import timezone
|
|
9
11
|
from threading import Lock
|
|
10
12
|
from typing import Any
|
|
11
13
|
from typing import cast
|
|
12
|
-
from typing import
|
|
14
|
+
from typing import Protocol
|
|
13
15
|
from typing import TYPE_CHECKING
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from typing import override
|
|
20
|
+
except ImportError:
|
|
21
|
+
from typing_extensions import override # type: ignore
|
|
15
22
|
|
|
16
23
|
from ulid import base32
|
|
17
24
|
from ulid import constants
|
|
@@ -19,7 +26,6 @@ from ulid import constants
|
|
|
19
26
|
|
|
20
27
|
if TYPE_CHECKING: # pragma: no cover
|
|
21
28
|
import sys
|
|
22
|
-
from collections.abc import Callable
|
|
23
29
|
|
|
24
30
|
from pydantic import GetCoreSchemaHandler
|
|
25
31
|
from pydantic import ValidatorFunctionWrapHandler
|
|
@@ -38,65 +44,202 @@ except ImportError: # pragma: no cover
|
|
|
38
44
|
|
|
39
45
|
__version__ = version("python-ulid")
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
|
|
48
|
+
RandomnessSource = Callable[[int], bytes]
|
|
43
49
|
|
|
44
50
|
|
|
45
|
-
class
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
class MonotonicityPolicy(Protocol):
|
|
52
|
+
"""Protocol defining the interface for monotonicity and randomness resolution policies."""
|
|
53
|
+
|
|
54
|
+
def resolve_randomness(
|
|
55
|
+
self,
|
|
56
|
+
timestamp: int,
|
|
57
|
+
randomness_source: RandomnessSource,
|
|
58
|
+
) -> bytes:
|
|
59
|
+
"""Resolve randomness for a given timestamp.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
timestamp (int): The current timestamp in milliseconds.
|
|
63
|
+
randomness_source (RandomnessSource): A callable to get fresh random bytes.
|
|
48
64
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
message = "Value has to be of type "
|
|
54
|
-
message += " or ".join([t.__name__ for t in self.types])
|
|
55
|
-
raise TypeError(message)
|
|
56
|
-
return func(cls, value)
|
|
65
|
+
Returns:
|
|
66
|
+
bytes: The resolved randomness bytes (80 bits).
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
57
69
|
|
|
58
|
-
return wrapped
|
|
59
70
|
|
|
71
|
+
class BaseMonotonicPolicy(abc.ABC):
|
|
72
|
+
"""Base class for stateful monotonic policies."""
|
|
60
73
|
|
|
61
|
-
class ValueProvider:
|
|
62
74
|
def __init__(self) -> None:
|
|
63
|
-
self.lock = Lock()
|
|
64
75
|
self.prev_timestamp = constants.MIN_TIMESTAMP
|
|
65
76
|
self.prev_randomness = constants.MIN_RANDOMNESS
|
|
66
77
|
|
|
67
|
-
def
|
|
78
|
+
def resolve_randomness(
|
|
79
|
+
self,
|
|
80
|
+
timestamp: int,
|
|
81
|
+
randomness_source: RandomnessSource,
|
|
82
|
+
) -> bytes:
|
|
83
|
+
if timestamp == self.prev_timestamp:
|
|
84
|
+
if self.prev_randomness == constants.MAX_RANDOMNESS:
|
|
85
|
+
randomness = self._on_overflow(timestamp, randomness_source)
|
|
86
|
+
else:
|
|
87
|
+
randomness = self._increment(self.prev_randomness)
|
|
88
|
+
else:
|
|
89
|
+
randomness = randomness_source(timestamp)
|
|
90
|
+
|
|
91
|
+
self.prev_randomness = randomness
|
|
92
|
+
self.prev_timestamp = timestamp
|
|
93
|
+
return randomness
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _increment(value: bytes) -> bytes:
|
|
97
|
+
return (int.from_bytes(value, byteorder="big") + 1).to_bytes(len(value), byteorder="big")
|
|
98
|
+
|
|
99
|
+
@abc.abstractmethod
|
|
100
|
+
def _on_overflow(
|
|
101
|
+
self,
|
|
102
|
+
timestamp: int,
|
|
103
|
+
randomness_source: RandomnessSource,
|
|
104
|
+
) -> bytes:
|
|
105
|
+
"""Handle same-millisecond randomness exhaustion.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
timestamp (int): The current timestamp in milliseconds.
|
|
109
|
+
randomness_source (RandomnessSource): A callable to get fresh random bytes.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
bytes: The resolved randomness bytes.
|
|
113
|
+
"""
|
|
114
|
+
raise NotImplementedError
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class StrictMonotonicPolicy(BaseMonotonicPolicy):
|
|
118
|
+
"""Strict monotonicity policy.
|
|
119
|
+
|
|
120
|
+
Always increments the randomness by 1 if generated within the same millisecond.
|
|
121
|
+
Raises ValueError on millisecond randomness exhaustion.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@override
|
|
125
|
+
def _on_overflow(
|
|
126
|
+
self,
|
|
127
|
+
timestamp: int,
|
|
128
|
+
randomness_source: RandomnessSource,
|
|
129
|
+
) -> bytes:
|
|
130
|
+
raise ValueError("Randomness within same millisecond exhausted")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class PureRandomPolicy:
|
|
134
|
+
"""Pure random policy.
|
|
135
|
+
|
|
136
|
+
Always generates fresh randomness without enforcing any monotonicity constraints.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def resolve_randomness(
|
|
140
|
+
self,
|
|
141
|
+
timestamp: int,
|
|
142
|
+
randomness_source: RandomnessSource,
|
|
143
|
+
) -> bytes:
|
|
144
|
+
return randomness_source(timestamp)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class LaxMonotonicPolicy(BaseMonotonicPolicy):
|
|
148
|
+
"""Lax monotonicity policy.
|
|
149
|
+
|
|
150
|
+
Increments the randomness by 1 if generated within the same millisecond.
|
|
151
|
+
If the randomness overflows, it regenerates fresh randomness instead of raising
|
|
152
|
+
an error or sleeping.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
def _on_overflow(
|
|
157
|
+
self,
|
|
158
|
+
timestamp: int,
|
|
159
|
+
randomness_source: RandomnessSource,
|
|
160
|
+
) -> bytes:
|
|
161
|
+
return randomness_source(timestamp)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ULIDGenerator:
|
|
165
|
+
"""Generator for creating universally unique lexicographically sortable identifiers (ULIDs).
|
|
166
|
+
|
|
167
|
+
Samples a clock for the timestamp, sources entropy for the randomness, and enforces a
|
|
168
|
+
:class:`MonotonicityPolicy` so that identifiers generated within the same millisecond are
|
|
169
|
+
monotonically increasing. Generation is guarded by a lock and is safe to share across
|
|
170
|
+
threads.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
clock: A callable returning the current time in milliseconds. Defaults to the system
|
|
174
|
+
clock.
|
|
175
|
+
randomness: A callable that, given a timestamp, returns fresh random bytes for the
|
|
176
|
+
randomness component. Defaults to :func:`os.urandom`.
|
|
177
|
+
policy: The :class:`MonotonicityPolicy` used to resolve randomness on same-millisecond
|
|
178
|
+
collisions. Defaults to :class:`StrictMonotonicPolicy`.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
clock: Callable[[], int] | None = None,
|
|
184
|
+
randomness: RandomnessSource | None = None,
|
|
185
|
+
policy: MonotonicityPolicy | None = None,
|
|
186
|
+
) -> None:
|
|
187
|
+
self.lock = Lock()
|
|
188
|
+
self.clock = clock or self._default_clock
|
|
189
|
+
self.randomness_source = randomness or self._default_randomness
|
|
190
|
+
self.policy = policy or StrictMonotonicPolicy()
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _default_clock() -> int:
|
|
194
|
+
return time.time_ns() // constants.NANOSECS_IN_MILLISECS
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _default_randomness(_timestamp: int) -> bytes:
|
|
198
|
+
return os.urandom(constants.RANDOMNESS_LEN)
|
|
199
|
+
|
|
200
|
+
def _normalize_timestamp(self, value: float | datetime | None = None) -> int:
|
|
68
201
|
if value is None:
|
|
69
|
-
value =
|
|
202
|
+
value = self.clock()
|
|
203
|
+
elif isinstance(value, datetime):
|
|
204
|
+
value = int(value.timestamp() * constants.MILLISECS_IN_SECS)
|
|
70
205
|
elif isinstance(value, float):
|
|
71
206
|
value = int(value * constants.MILLISECS_IN_SECS)
|
|
72
207
|
if value > constants.MAX_TIMESTAMP:
|
|
73
208
|
raise ValueError("Value exceeds maximum possible timestamp")
|
|
74
209
|
return value
|
|
75
210
|
|
|
76
|
-
def
|
|
211
|
+
def generate(self, timestamp: float | datetime | None = None) -> ULID:
|
|
212
|
+
"""Generate a new :class:`ULID` monotonically.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
timestamp (int, float, datetime, None): Optional timestamp to set on the ULID.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
ULID: A generated ULID.
|
|
219
|
+
"""
|
|
220
|
+
ts = self._normalize_timestamp(timestamp)
|
|
221
|
+
|
|
77
222
|
with self.lock:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
randomness = os.urandom(constants.RANDOMNESS_LEN)
|
|
223
|
+
randomness = self.policy.resolve_randomness(
|
|
224
|
+
ts,
|
|
225
|
+
self.randomness_source,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
ts_bytes = int.to_bytes(ts, constants.TIMESTAMP_LEN, "big")
|
|
229
|
+
return ULID.from_bytes(ts_bytes + randomness)
|
|
86
230
|
|
|
87
|
-
self.prev_randomness = randomness
|
|
88
|
-
self.prev_timestamp = current_timestamp
|
|
89
|
-
return randomness
|
|
90
231
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
232
|
+
#: The module-level generator used by ``ULID()`` and the ``ULID.from_*`` constructors.
|
|
233
|
+
#: Reassign it to route the default constructors through a custom
|
|
234
|
+
#: :class:`ULIDGenerator` (e.g. a different clock, randomness source, or
|
|
235
|
+
#: :class:`MonotonicityPolicy`)::
|
|
236
|
+
#:
|
|
237
|
+
#: ulid.default_generator = ULIDGenerator(policy=LaxMonotonicPolicy())
|
|
238
|
+
default_generator = ULIDGenerator()
|
|
94
239
|
|
|
95
240
|
|
|
96
241
|
@functools.total_ordering
|
|
97
242
|
class ULID:
|
|
98
|
-
provider = ValueProvider()
|
|
99
|
-
|
|
100
243
|
"""The :class:`ULID` object consists of a timestamp part of 48 bits and of 80 random bits.
|
|
101
244
|
|
|
102
245
|
.. code-block:: text
|
|
@@ -121,13 +264,18 @@ class ULID:
|
|
|
121
264
|
ValueError: If the provided value is not a valid encoded ULID.
|
|
122
265
|
"""
|
|
123
266
|
|
|
124
|
-
def __init__(
|
|
267
|
+
def __init__(
|
|
268
|
+
self,
|
|
269
|
+
value: bytes | None = None,
|
|
270
|
+
) -> None:
|
|
125
271
|
if value is not None and len(value) != constants.BYTES_LEN:
|
|
126
272
|
raise ValueError("ULID has to be exactly 16 bytes long.")
|
|
127
|
-
|
|
273
|
+
if value is None:
|
|
274
|
+
self.bytes: bytes = default_generator.generate().bytes
|
|
275
|
+
else:
|
|
276
|
+
self.bytes = value
|
|
128
277
|
|
|
129
278
|
@classmethod
|
|
130
|
-
@validate_type(datetime)
|
|
131
279
|
def from_datetime(cls, value: datetime) -> Self:
|
|
132
280
|
"""Create a new :class:`ULID`-object from a :class:`datetime`. The timestamp part of the
|
|
133
281
|
`ULID` will be set to the corresponding timestamp of the datetime.
|
|
@@ -138,10 +286,11 @@ class ULID:
|
|
|
138
286
|
>>> ULID.from_datetime(datetime.now())
|
|
139
287
|
ULID(01E75QRYCAMM1MKQ9NYMYT6SAV)
|
|
140
288
|
"""
|
|
141
|
-
|
|
289
|
+
if not isinstance(value, datetime):
|
|
290
|
+
raise TypeError("Value has to be of type datetime")
|
|
291
|
+
return cls.from_bytes(default_generator.generate(value).bytes)
|
|
142
292
|
|
|
143
293
|
@classmethod
|
|
144
|
-
@validate_type(int, float)
|
|
145
294
|
def from_timestamp(cls, value: float) -> Self:
|
|
146
295
|
"""Create a new :class:`ULID`-object from a timestamp. The timestamp can be either a
|
|
147
296
|
`float` representing the time in seconds (as it would be returned by :func:`time.time()`)
|
|
@@ -153,13 +302,11 @@ class ULID:
|
|
|
153
302
|
>>> ULID.from_timestamp(time.time())
|
|
154
303
|
ULID(01E75QWN5HKQ0JAVX9FG1K4YP4)
|
|
155
304
|
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return cls.from_bytes(timestamp + randomness)
|
|
305
|
+
if not isinstance(value, (int, float)):
|
|
306
|
+
raise TypeError("Value has to be of type int or float")
|
|
307
|
+
return cls.from_bytes(default_generator.generate(value).bytes)
|
|
160
308
|
|
|
161
309
|
@classmethod
|
|
162
|
-
@validate_type(uuid.UUID)
|
|
163
310
|
def from_uuid(cls, value: uuid.UUID) -> Self:
|
|
164
311
|
"""Create a new :class:`ULID`-object from a :class:`uuid.UUID`. The timestamp part will be
|
|
165
312
|
random in that case.
|
|
@@ -170,30 +317,36 @@ class ULID:
|
|
|
170
317
|
>>> ULID.from_uuid(uuid4())
|
|
171
318
|
ULID(27Q506DP7E9YNRXA0XVD8Z5YSG)
|
|
172
319
|
"""
|
|
320
|
+
if not isinstance(value, uuid.UUID):
|
|
321
|
+
raise TypeError("Value has to be of type UUID")
|
|
173
322
|
return cls(value.bytes)
|
|
174
323
|
|
|
175
324
|
@classmethod
|
|
176
|
-
@validate_type(bytes)
|
|
177
325
|
def from_bytes(cls, bytes_: bytes) -> Self:
|
|
178
326
|
"""Create a new :class:`ULID`-object from sequence of 16 bytes."""
|
|
327
|
+
if not isinstance(bytes_, bytes):
|
|
328
|
+
raise TypeError("Value has to be of type bytes")
|
|
179
329
|
return cls(bytes_)
|
|
180
330
|
|
|
181
331
|
@classmethod
|
|
182
|
-
@validate_type(str)
|
|
183
332
|
def from_hex(cls, value: str) -> Self:
|
|
184
333
|
"""Create a new :class:`ULID`-object from 32 character string of hex values."""
|
|
334
|
+
if not isinstance(value, str):
|
|
335
|
+
raise TypeError("Value has to be of type str")
|
|
185
336
|
return cls.from_bytes(bytes.fromhex(value))
|
|
186
337
|
|
|
187
338
|
@classmethod
|
|
188
|
-
@validate_type(str)
|
|
189
339
|
def from_str(cls, string: str) -> Self:
|
|
190
340
|
"""Create a new :class:`ULID`-object from a 26 char long string representation."""
|
|
341
|
+
if not isinstance(string, str):
|
|
342
|
+
raise TypeError("Value has to be of type str")
|
|
191
343
|
return cls(base32.decode(string))
|
|
192
344
|
|
|
193
345
|
@classmethod
|
|
194
|
-
@validate_type(int)
|
|
195
346
|
def from_int(cls, value: int) -> Self:
|
|
196
347
|
"""Create a new :class:`ULID`-object from an `int`."""
|
|
348
|
+
if not isinstance(value, int):
|
|
349
|
+
raise TypeError("Value has to be of type int")
|
|
197
350
|
return cls(int.to_bytes(value, constants.BYTES_LEN, "big"))
|
|
198
351
|
|
|
199
352
|
@classmethod
|
|
@@ -334,7 +487,6 @@ class ULID:
|
|
|
334
487
|
return uuid.UUID(bytes=uuid_bytes)
|
|
335
488
|
|
|
336
489
|
@classmethod
|
|
337
|
-
@validate_type(uuid.UUID)
|
|
338
490
|
def from_uuidv7(cls, value: uuid.UUID) -> Self:
|
|
339
491
|
"""Create a new :class:`ULID` from a UUIDv7 (:class:`uuid.UUID` version 7).
|
|
340
492
|
|
|
@@ -349,6 +501,8 @@ class ULID:
|
|
|
349
501
|
>>> ulid.datetime
|
|
350
502
|
datetime.datetime(2025, 11, 10, ...)
|
|
351
503
|
"""
|
|
504
|
+
if not isinstance(value, uuid.UUID):
|
|
505
|
+
raise TypeError("Value has to be of type UUID")
|
|
352
506
|
uuid_int = int.from_bytes(value.bytes, byteorder="big")
|
|
353
507
|
|
|
354
508
|
# Extract timestamp from UUIDv7 layout (always in first 48 bits)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
ulid/__init__.py,sha256=MokfVXGP3iguVkHuzaJvLeFytWAqe_jAqrQqzWYDbRs,16352
|
|
2
|
-
ulid/__main__.py,sha256=ZP8ZbhQ_o-O4Ckbba87bZ4NkwzrSFhAkrRLmUnzkW0Y,5586
|
|
3
|
-
ulid/base32.py,sha256=iUE5BGPW5Dul-Eu3ITvcymk8acNkIFAVTlrL12YIPKI,5961
|
|
4
|
-
ulid/constants.py,sha256=lbCLRNQXq8qnOdz_w-nJf_j5dC2X7Gjv-8GJUbs5jX8,503
|
|
5
|
-
ulid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
python_ulid-3.2.1.dist-info/METADATA,sha256=_Ug09XKRumD9YAhF2abhShiUXJLRjfkh8HebBZPQBb8,6591
|
|
7
|
-
python_ulid-3.2.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
-
python_ulid-3.2.1.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
|
|
9
|
-
python_ulid-3.2.1.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
|
|
10
|
-
python_ulid-3.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|