python-ulid 2.7.0__py3-none-any.whl → 3.1.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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: python-ulid
3
- Version: 2.7.0
3
+ Version: 3.1.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
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.9
22
22
  Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
25
26
  Classifier: Topic :: Software Development :: Libraries
26
27
  Requires-Python: >=3.9
27
28
  Provides-Extra: pydantic
@@ -0,0 +1,10 @@
1
+ ulid/__init__.py,sha256=g9Fb4nyq2mt3wGtLdmv3h9HutFJXLsrv_3HiVM-Qph8,12419
2
+ ulid/__main__.py,sha256=TUo3dkWkJLfzRJyd55nfqGqvv1NcG7fBpsZHIi_p4rw,5421
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.1.0.dist-info/METADATA,sha256=TM_dpKEq830WTj6mahb6qF_5wiVnL6-Rau-6REqSoSQ,5833
7
+ python_ulid-3.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ python_ulid-3.1.0.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
9
+ python_ulid-3.1.0.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
10
+ python_ulid-3.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.2
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
ulid/__init__.py CHANGED
@@ -4,19 +4,24 @@ import functools
4
4
  import os
5
5
  import time
6
6
  import uuid
7
- from collections.abc import Callable
8
7
  from datetime import datetime
9
8
  from datetime import timezone
9
+ from threading import Lock
10
10
  from typing import Any
11
+ from typing import cast
11
12
  from typing import Generic
12
13
  from typing import TYPE_CHECKING
13
14
  from typing import TypeVar
14
15
 
16
+ from typing_extensions import Self
17
+
15
18
  from ulid import base32
16
19
  from ulid import constants
17
20
 
18
21
 
19
22
  if TYPE_CHECKING: # pragma: no cover
23
+ from collections.abc import Callable
24
+
20
25
  from pydantic import GetCoreSchemaHandler
21
26
  from pydantic import ValidatorFunctionWrapHandler
22
27
  from pydantic_core import CoreSchema
@@ -29,7 +34,6 @@ except ImportError: # pragma: no cover
29
34
 
30
35
  __version__ = version("python-ulid")
31
36
 
32
-
33
37
  T = TypeVar("T", bound=type)
34
38
  R = TypeVar("R")
35
39
 
@@ -44,17 +48,50 @@ class validate_type(Generic[T]): # noqa: N801
44
48
  if not isinstance(value, self.types):
45
49
  message = "Value has to be of type "
46
50
  message += " or ".join([t.__name__ for t in self.types])
47
- raise ValueError(message)
51
+ raise TypeError(message)
48
52
  return func(cls, value)
49
53
 
50
54
  return wrapped
51
55
 
52
56
 
53
- U = TypeVar("U", bound="ULID")
57
+ class ValueProvider:
58
+ def __init__(self) -> None:
59
+ self.lock = Lock()
60
+ self.prev_timestamp = constants.MIN_TIMESTAMP
61
+ self.prev_randomness = constants.MIN_RANDOMNESS
62
+
63
+ def timestamp(self, value: float | None = None) -> int:
64
+ if value is None:
65
+ value = time.time_ns() // constants.NANOSECS_IN_MILLISECS
66
+ elif isinstance(value, float):
67
+ value = int(value * constants.MILLISECS_IN_SECS)
68
+ if value > constants.MAX_TIMESTAMP:
69
+ raise ValueError("Value exceeds maximum possible timestamp")
70
+ return value
71
+
72
+ def randomness(self) -> bytes:
73
+ with self.lock:
74
+ current_timestamp = self.timestamp()
75
+ if current_timestamp == self.prev_timestamp:
76
+ if self.prev_randomness == constants.MAX_RANDOMNESS:
77
+ raise ValueError("Randomness within same millisecond exhausted")
78
+ randomness = self.increment_bytes(self.prev_randomness)
79
+ else:
80
+ randomness = os.urandom(constants.RANDOMNESS_LEN)
81
+
82
+ self.prev_randomness = randomness
83
+ self.prev_timestamp = current_timestamp
84
+ return randomness
85
+
86
+ def increment_bytes(self, value: bytes) -> bytes:
87
+ length = len(value)
88
+ return (int.from_bytes(value, byteorder="big") + 1).to_bytes(length, byteorder="big")
54
89
 
55
90
 
56
91
  @functools.total_ordering
57
92
  class ULID:
93
+ provider = ValueProvider()
94
+
58
95
  """The :class:`ULID` object consists of a timestamp part of 48 bits and of 80 random bits.
59
96
 
60
97
  .. code-block:: text
@@ -82,13 +119,11 @@ class ULID:
82
119
  def __init__(self, value: bytes | None = None) -> None:
83
120
  if value is not None and len(value) != constants.BYTES_LEN:
84
121
  raise ValueError("ULID has to be exactly 16 bytes long.")
85
- self.bytes: bytes = (
86
- value or ULID.from_timestamp(time.time_ns() // constants.NANOSECS_IN_MILLISECS).bytes
87
- )
122
+ self.bytes: bytes = value or ULID.from_timestamp(self.provider.timestamp()).bytes
88
123
 
89
124
  @classmethod
90
125
  @validate_type(datetime)
91
- def from_datetime(cls: type[U], value: datetime) -> U:
126
+ def from_datetime(cls, value: datetime) -> Self:
92
127
  """Create a new :class:`ULID`-object from a :class:`datetime`. The timestamp part of the
93
128
  `ULID` will be set to the corresponding timestamp of the datetime.
94
129
 
@@ -102,7 +137,7 @@ class ULID:
102
137
 
103
138
  @classmethod
104
139
  @validate_type(int, float)
105
- def from_timestamp(cls: type[U], value: int | float) -> U:
140
+ def from_timestamp(cls, value: float) -> Self:
106
141
  """Create a new :class:`ULID`-object from a timestamp. The timestamp can be either a
107
142
  `float` representing the time in seconds (as it would be returned by :func:`time.time()`)
108
143
  or an `int` in milliseconds.
@@ -113,15 +148,13 @@ class ULID:
113
148
  >>> ULID.from_timestamp(time.time())
114
149
  ULID(01E75QWN5HKQ0JAVX9FG1K4YP4)
115
150
  """
116
- if isinstance(value, float):
117
- value = int(value * constants.MILLISECS_IN_SECS)
118
- timestamp = int.to_bytes(value, constants.TIMESTAMP_LEN, "big")
119
- randomness = os.urandom(constants.RANDOMNESS_LEN)
151
+ timestamp = int.to_bytes(cls.provider.timestamp(value), constants.TIMESTAMP_LEN, "big")
152
+ randomness = cls.provider.randomness()
120
153
  return cls.from_bytes(timestamp + randomness)
121
154
 
122
155
  @classmethod
123
156
  @validate_type(uuid.UUID)
124
- def from_uuid(cls: type[U], value: uuid.UUID) -> U:
157
+ def from_uuid(cls, value: uuid.UUID) -> Self:
125
158
  """Create a new :class:`ULID`-object from a :class:`uuid.UUID`. The timestamp part will be
126
159
  random in that case.
127
160
 
@@ -135,40 +168,73 @@ class ULID:
135
168
 
136
169
  @classmethod
137
170
  @validate_type(bytes)
138
- def from_bytes(cls: type[U], bytes_: bytes) -> U:
171
+ def from_bytes(cls, bytes_: bytes) -> Self:
139
172
  """Create a new :class:`ULID`-object from sequence of 16 bytes."""
140
173
  return cls(bytes_)
141
174
 
142
175
  @classmethod
143
176
  @validate_type(str)
144
- def from_hex(cls: type[U], value: str) -> U:
177
+ def from_hex(cls, value: str) -> Self:
145
178
  """Create a new :class:`ULID`-object from 32 character string of hex values."""
146
179
  return cls.from_bytes(bytes.fromhex(value))
147
180
 
148
181
  @classmethod
149
182
  @validate_type(str)
150
- def from_str(cls: type[U], string: str) -> U:
183
+ def from_str(cls, string: str) -> Self:
151
184
  """Create a new :class:`ULID`-object from a 26 char long string representation."""
152
185
  return cls(base32.decode(string))
153
186
 
154
187
  @classmethod
155
188
  @validate_type(int)
156
- def from_int(cls: type[U], value: int) -> U:
189
+ def from_int(cls, value: int) -> Self:
157
190
  """Create a new :class:`ULID`-object from an `int`."""
158
191
  return cls(int.to_bytes(value, constants.BYTES_LEN, "big"))
159
192
 
160
- @property
193
+ @classmethod
194
+ def parse(cls, value: Any) -> Self:
195
+ """Create a new :class:`ULID`-object from a given value.
196
+
197
+ .. note::
198
+ This method should only be used when the caller is trying to parse a ULID from
199
+ a value when they're unsure what format/primitive type it will be given in.
200
+ """
201
+ if isinstance(value, ULID):
202
+ return cast(Self, value)
203
+ if isinstance(value, uuid.UUID):
204
+ return cls.from_uuid(value)
205
+ if isinstance(value, str):
206
+ len_value = len(value)
207
+ if len_value == constants.UUID_REPR_LEN:
208
+ return cls.from_uuid(uuid.UUID(value))
209
+ if len_value == constants.HEX_REPR_LEN:
210
+ return cls.from_hex(value)
211
+ if len_value == constants.REPR_LEN:
212
+ return cls.from_str(value)
213
+ raise ValueError(f"Cannot parse ULID from string of length {len_value}")
214
+ if isinstance(value, int):
215
+ if len(str(value)) == constants.INT_REPR_LEN:
216
+ return cls.from_int(value)
217
+ return cls.from_timestamp(value)
218
+ if isinstance(value, float):
219
+ return cls.from_timestamp(value)
220
+ if isinstance(value, datetime):
221
+ return cls.from_datetime(value)
222
+ if isinstance(value, bytes):
223
+ return cls.from_bytes(value)
224
+ raise TypeError(f"Cannot parse ULID from type {type(value)}")
225
+
226
+ @functools.cached_property
161
227
  def milliseconds(self) -> int:
162
228
  """The timestamp part as epoch time in milliseconds.
163
229
 
164
230
  Examples:
165
231
 
166
- >>> ulid.timestamp
232
+ >>> ulid.milliseconds
167
233
  1588257207560
168
234
  """
169
235
  return int.from_bytes(self.bytes[: constants.TIMESTAMP_LEN], byteorder="big")
170
236
 
171
- @property
237
+ @functools.cached_property
172
238
  def timestamp(self) -> float:
173
239
  """The timestamp part as epoch time in seconds.
174
240
 
@@ -179,7 +245,7 @@ class ULID:
179
245
  """
180
246
  return self.milliseconds / constants.MILLISECS_IN_SECS
181
247
 
182
- @property
248
+ @functools.cached_property
183
249
  def datetime(self) -> datetime:
184
250
  """Return the timestamp part as timezone-aware :class:`datetime` in UTC.
185
251
 
@@ -190,7 +256,7 @@ class ULID:
190
256
  """
191
257
  return datetime.fromtimestamp(self.timestamp, timezone.utc)
192
258
 
193
- @property
259
+ @functools.cached_property
194
260
  def hex(self) -> str:
195
261
  """Encode the :class:`ULID`-object as a 32 char sequence of hex values."""
196
262
  return self.bytes.hex()
@@ -233,22 +299,22 @@ class ULID:
233
299
  def __lt__(self, other: Any) -> bool:
234
300
  if isinstance(other, ULID):
235
301
  return self.bytes < other.bytes
236
- elif isinstance(other, int):
302
+ if isinstance(other, int):
237
303
  return int(self) < other
238
- elif isinstance(other, bytes):
304
+ if isinstance(other, bytes):
239
305
  return self.bytes < other
240
- elif isinstance(other, str):
306
+ if isinstance(other, str):
241
307
  return str(self) < other
242
308
  return NotImplemented
243
309
 
244
- def __eq__(self, other: Any) -> bool:
310
+ def __eq__(self, other: object) -> bool:
245
311
  if isinstance(other, ULID):
246
312
  return self.bytes == other.bytes
247
- elif isinstance(other, int):
313
+ if isinstance(other, int):
248
314
  return int(self) == other
249
- elif isinstance(other, bytes):
315
+ if isinstance(other, bytes):
250
316
  return self.bytes == other
251
- elif isinstance(other, str):
317
+ if isinstance(other, str):
252
318
  return str(self) == other
253
319
  return NotImplemented
254
320
 
@@ -261,14 +327,16 @@ class ULID:
261
327
 
262
328
  return core_schema.no_info_wrap_validator_function(
263
329
  cls._pydantic_validate,
264
- core_schema.union_schema(
265
- [
266
- core_schema.is_instance_schema(ULID),
267
- core_schema.no_info_plain_validator_function(ULID),
268
- core_schema.str_schema(pattern=r"[A-Z0-9]{26}", min_length=26, max_length=26),
269
- core_schema.bytes_schema(min_length=16, max_length=16),
270
- ]
271
- ),
330
+ core_schema.union_schema([
331
+ core_schema.is_instance_schema(ULID),
332
+ core_schema.no_info_plain_validator_function(ULID),
333
+ core_schema.str_schema(
334
+ pattern=rf"[0-7][{base32.ENCODE}]{{25}}",
335
+ min_length=26,
336
+ max_length=26,
337
+ ),
338
+ core_schema.bytes_schema(min_length=16, max_length=16),
339
+ ]),
272
340
  serialization=core_schema.to_string_ser_schema(
273
341
  when_used="json-unless-none",
274
342
  ),
@@ -278,6 +346,7 @@ class ULID:
278
346
  def _pydantic_validate(cls, value: Any, handler: ValidatorFunctionWrapHandler) -> Any:
279
347
  from pydantic_core import PydanticCustomError
280
348
 
349
+ ulid: ULID
281
350
  try:
282
351
  if isinstance(value, int):
283
352
  ulid = cls.from_int(value)
ulid/__main__.py CHANGED
@@ -4,17 +4,21 @@ import argparse
4
4
  import shutil
5
5
  import sys
6
6
  import textwrap
7
- from collections.abc import Callable
8
- from collections.abc import Sequence
9
7
  from datetime import datetime
10
8
  from functools import partial
11
9
  from typing import Any
10
+ from typing import TYPE_CHECKING
12
11
  from uuid import UUID
13
12
 
14
13
  import ulid
15
14
  from ulid import ULID
16
15
 
17
16
 
17
+ if TYPE_CHECKING: # pragma: no cover
18
+ from collections.abc import Callable
19
+ from collections.abc import Sequence
20
+
21
+
18
22
  def make_parser(prog: str | None = None) -> argparse.ArgumentParser:
19
23
  parser = argparse.ArgumentParser(
20
24
  prog=prog,
@@ -135,30 +139,30 @@ def show(args: argparse.Namespace) -> str:
135
139
  ulid: ULID = ULID.from_str(from_value_or_stdin(args.ulid))
136
140
  if args.uuid:
137
141
  return str(ulid.to_uuid())
138
- elif args.uuid4:
142
+ if args.uuid4:
139
143
  return str(ulid.to_uuid4())
140
- elif args.hex:
144
+ if args.hex:
141
145
  return ulid.hex
142
- elif args.int:
146
+ if args.int:
143
147
  return str(int(ulid))
144
- elif args.timestamp:
148
+ if args.timestamp:
145
149
  return str(ulid.timestamp)
146
- elif args.datetime:
150
+ if args.datetime:
147
151
  return ulid.datetime.isoformat()
148
- else:
149
- return textwrap.dedent(
150
- f"""
151
- ULID: {ulid!s}
152
- Hex: {ulid.hex}
153
- Int: {int(ulid)}
154
- Timestamp: {ulid.timestamp}
155
- Datetime: {ulid.datetime.isoformat()}
156
- """
157
- ).strip()
152
+ return textwrap.dedent(
153
+ f"""
154
+ ULID: {ulid!s}
155
+ Hex: {ulid.hex}
156
+ Int: {int(ulid)}
157
+ Timestamp: {ulid.timestamp}
158
+ Datetime: {ulid.datetime.isoformat()}
159
+ """
160
+ ).strip()
158
161
 
159
162
 
160
163
  def entrypoint() -> None: # pragma: no cover
161
- print(main(sys.argv[1:]))
164
+ if (value := main(sys.argv[1:])) is not None:
165
+ print(value) # noqa: T201
162
166
 
163
167
 
164
168
  if __name__ == "__main__": # pragma: no cover
ulid/base32.py CHANGED
@@ -153,46 +153,42 @@ def encode_timestamp(binary: bytes) -> str:
153
153
  if len(binary) != constants.TIMESTAMP_LEN:
154
154
  raise ValueError("Timestamp value has to be exactly 6 bytes long.")
155
155
  lut = ENCODE
156
- return "".join(
157
- [
158
- lut[(binary[0] & 224) >> 5],
159
- lut[(binary[0] & 31)],
160
- lut[(binary[1] & 248) >> 3],
161
- lut[((binary[1] & 7) << 2) | ((binary[2] & 192) >> 6)],
162
- lut[((binary[2] & 62) >> 1)],
163
- lut[((binary[2] & 1) << 4) | ((binary[3] & 240) >> 4)],
164
- lut[((binary[3] & 15) << 1) | ((binary[4] & 128) >> 7)],
165
- lut[(binary[4] & 124) >> 2],
166
- lut[((binary[4] & 3) << 3) | ((binary[5] & 224) >> 5)],
167
- lut[(binary[5] & 31)],
168
- ]
169
- )
156
+ return "".join([
157
+ lut[(binary[0] & 224) >> 5],
158
+ lut[(binary[0] & 31)],
159
+ lut[(binary[1] & 248) >> 3],
160
+ lut[((binary[1] & 7) << 2) | ((binary[2] & 192) >> 6)],
161
+ lut[((binary[2] & 62) >> 1)],
162
+ lut[((binary[2] & 1) << 4) | ((binary[3] & 240) >> 4)],
163
+ lut[((binary[3] & 15) << 1) | ((binary[4] & 128) >> 7)],
164
+ lut[(binary[4] & 124) >> 2],
165
+ lut[((binary[4] & 3) << 3) | ((binary[5] & 224) >> 5)],
166
+ lut[(binary[5] & 31)],
167
+ ])
170
168
 
171
169
 
172
170
  def encode_randomness(binary: bytes) -> str:
173
171
  if len(binary) != constants.RANDOMNESS_LEN:
174
172
  raise ValueError("Randomness value has to be exactly 10 bytes long.")
175
173
  lut = ENCODE
176
- return "".join(
177
- [
178
- lut[(binary[0] & 248) >> 3],
179
- lut[((binary[0] & 7) << 2) | ((binary[1] & 192) >> 6)],
180
- lut[(binary[1] & 62) >> 1],
181
- lut[((binary[1] & 1) << 4) | ((binary[2] & 240) >> 4)],
182
- lut[((binary[2] & 15) << 1) | ((binary[3] & 128) >> 7)],
183
- lut[(binary[3] & 124) >> 2],
184
- lut[((binary[3] & 3) << 3) | ((binary[4] & 224) >> 5)],
185
- lut[(binary[4] & 31)],
186
- lut[(binary[5] & 248) >> 3],
187
- lut[((binary[5] & 7) << 2) | ((binary[6] & 192) >> 6)],
188
- lut[(binary[6] & 62) >> 1],
189
- lut[((binary[6] & 1) << 4) | ((binary[7] & 240) >> 4)],
190
- lut[((binary[7] & 15) << 1) | ((binary[8] & 128) >> 7)],
191
- lut[(binary[8] & 124) >> 2],
192
- lut[((binary[8] & 3) << 3) | ((binary[9] & 224) >> 5)],
193
- lut[(binary[9] & 31)],
194
- ]
195
- )
174
+ return "".join([
175
+ lut[(binary[0] & 248) >> 3],
176
+ lut[((binary[0] & 7) << 2) | ((binary[1] & 192) >> 6)],
177
+ lut[(binary[1] & 62) >> 1],
178
+ lut[((binary[1] & 1) << 4) | ((binary[2] & 240) >> 4)],
179
+ lut[((binary[2] & 15) << 1) | ((binary[3] & 128) >> 7)],
180
+ lut[(binary[3] & 124) >> 2],
181
+ lut[((binary[3] & 3) << 3) | ((binary[4] & 224) >> 5)],
182
+ lut[(binary[4] & 31)],
183
+ lut[(binary[5] & 248) >> 3],
184
+ lut[((binary[5] & 7) << 2) | ((binary[6] & 192) >> 6)],
185
+ lut[(binary[6] & 62) >> 1],
186
+ lut[((binary[6] & 1) << 4) | ((binary[7] & 240) >> 4)],
187
+ lut[((binary[7] & 15) << 1) | ((binary[8] & 128) >> 7)],
188
+ lut[(binary[8] & 124) >> 2],
189
+ lut[((binary[8] & 3) << 3) | ((binary[9] & 224) >> 5)],
190
+ lut[(binary[9] & 31)],
191
+ ])
196
192
 
197
193
 
198
194
  def decode(encoded: str) -> bytes:
@@ -211,18 +207,16 @@ def decode_timestamp(encoded: str) -> bytes:
211
207
  lut = DECODE
212
208
  values: bytes = bytes(encoded, "ascii")
213
209
  # https://github.com/ulid/spec?tab=readme-ov-file#overflow-errors-when-parsing-base32-strings
214
- if lut[values[0]] > 7:
210
+ if lut[values[0]] > 7: # noqa: PLR2004
215
211
  raise ValueError(f"Timestamp value {encoded} is too large and will overflow 128-bits.")
216
- return bytes(
217
- [
218
- ((lut[values[0]] << 5) | lut[values[1]]) & 0xFF,
219
- ((lut[values[2]] << 3) | (lut[values[3]] >> 2)) & 0xFF,
220
- ((lut[values[3]] << 6) | (lut[values[4]] << 1) | (lut[values[5]] >> 4)) & 0xFF,
221
- ((lut[values[5]] << 4) | (lut[values[6]] >> 1)) & 0xFF,
222
- ((lut[values[6]] << 7) | (lut[values[7]] << 2) | (lut[values[8]] >> 3)) & 0xFF,
223
- ((lut[values[8]] << 5) | (lut[values[9]])) & 0xFF,
224
- ]
225
- )
212
+ return bytes([
213
+ ((lut[values[0]] << 5) | lut[values[1]]) & 0xFF,
214
+ ((lut[values[2]] << 3) | (lut[values[3]] >> 2)) & 0xFF,
215
+ ((lut[values[3]] << 6) | (lut[values[4]] << 1) | (lut[values[5]] >> 4)) & 0xFF,
216
+ ((lut[values[5]] << 4) | (lut[values[6]] >> 1)) & 0xFF,
217
+ ((lut[values[6]] << 7) | (lut[values[7]] << 2) | (lut[values[8]] >> 3)) & 0xFF,
218
+ ((lut[values[8]] << 5) | (lut[values[9]])) & 0xFF,
219
+ ])
226
220
 
227
221
 
228
222
  def decode_randomness(encoded: str) -> bytes:
@@ -230,17 +224,15 @@ def decode_randomness(encoded: str) -> bytes:
230
224
  raise ValueError("ULID randomness has to be exactly 16 characters long.")
231
225
  lut = DECODE
232
226
  values = bytes(encoded, "ascii")
233
- return bytes(
234
- [
235
- ((lut[values[0]] << 3) | (lut[values[1]] >> 2)) & 0xFF,
236
- ((lut[values[1]] << 6) | (lut[values[2]] << 1) | (lut[values[3]] >> 4)) & 0xFF,
237
- ((lut[values[3]] << 4) | (lut[values[4]] >> 1)) & 0xFF,
238
- ((lut[values[4]] << 7) | (lut[values[5]] << 2) | (lut[values[6]] >> 3)) & 0xFF,
239
- ((lut[values[6]] << 5) | (lut[values[7]])) & 0xFF,
240
- ((lut[values[8]] << 3) | (lut[values[9]] >> 2)) & 0xFF,
241
- ((lut[values[9]] << 6) | (lut[values[10]] << 1) | (lut[values[11]] >> 4)) & 0xFF,
242
- ((lut[values[11]] << 4) | (lut[values[12]] >> 1)) & 0xFF,
243
- ((lut[values[12]] << 7) | (lut[values[13]] << 2) | (lut[values[14]] >> 3)) & 0xFF,
244
- ((lut[values[14]] << 5) | (lut[values[15]])) & 0xFF,
245
- ]
246
- )
227
+ return bytes([
228
+ ((lut[values[0]] << 3) | (lut[values[1]] >> 2)) & 0xFF,
229
+ ((lut[values[1]] << 6) | (lut[values[2]] << 1) | (lut[values[3]] >> 4)) & 0xFF,
230
+ ((lut[values[3]] << 4) | (lut[values[4]] >> 1)) & 0xFF,
231
+ ((lut[values[4]] << 7) | (lut[values[5]] << 2) | (lut[values[6]] >> 3)) & 0xFF,
232
+ ((lut[values[6]] << 5) | (lut[values[7]])) & 0xFF,
233
+ ((lut[values[8]] << 3) | (lut[values[9]] >> 2)) & 0xFF,
234
+ ((lut[values[9]] << 6) | (lut[values[10]] << 1) | (lut[values[11]] >> 4)) & 0xFF,
235
+ ((lut[values[11]] << 4) | (lut[values[12]] >> 1)) & 0xFF,
236
+ ((lut[values[12]] << 7) | (lut[values[13]] << 2) | (lut[values[14]] >> 3)) & 0xFF,
237
+ ((lut[values[14]] << 5) | (lut[values[15]])) & 0xFF,
238
+ ])
ulid/constants.py CHANGED
@@ -1,6 +1,12 @@
1
1
  MILLISECS_IN_SECS = 1000
2
2
  NANOSECS_IN_MILLISECS = 1000000
3
3
 
4
+ MIN_RANDOMNESS = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
5
+ MAX_RANDOMNESS = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
6
+
7
+ MIN_TIMESTAMP = 0
8
+ MAX_TIMESTAMP = 2**48 - 1
9
+
4
10
  TIMESTAMP_LEN = 6
5
11
  RANDOMNESS_LEN = 10
6
12
  BYTES_LEN = TIMESTAMP_LEN + RANDOMNESS_LEN
@@ -8,3 +14,8 @@ BYTES_LEN = TIMESTAMP_LEN + RANDOMNESS_LEN
8
14
  TIMESTAMP_REPR_LEN = 10
9
15
  RANDOMNESS_REPR_LEN = 16
10
16
  REPR_LEN = TIMESTAMP_REPR_LEN + RANDOMNESS_REPR_LEN
17
+
18
+ INT_REPR_LEN = 37
19
+
20
+ HEX_REPR_LEN = 32
21
+ UUID_REPR_LEN = 36 # UUID with dash-separated segments
@@ -1,10 +0,0 @@
1
- ulid/__init__.py,sha256=tPyxks3cQzUiqkRdLgoDuunqiz090RiFkUfCMGxw7fU,9588
2
- ulid/__main__.py,sha256=Cmg0NEz3GUZ2WhsZVpY3-Kq-JOkxJFaLsRNgj3Aosyw,5341
3
- ulid/base32.py,sha256=cMj7Ye9qRdWn3n3hwaEXGMi2OkqmleAAxE20y7qmBqI,6184
4
- ulid/constants.py,sha256=N_nw4W2ciXO8C1IeuO3YK3j4_x9QW-0KyvrSQUuM-G4,241
5
- ulid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- python_ulid-2.7.0.dist-info/METADATA,sha256=zJASFWDrkYVNYfnjjA70QGCC_kyzfyufEog6Ig4emH8,5782
7
- python_ulid-2.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
8
- python_ulid-2.7.0.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
9
- python_ulid-2.7.0.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
10
- python_ulid-2.7.0.dist-info/RECORD,,