python-ulid 2.6.0__py3-none-any.whl → 3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-ulid
3
- Version: 2.6.0
3
+ Version: 3.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
@@ -0,0 +1,10 @@
1
+ ulid/__init__.py,sha256=yoBtXpTz-t8xnfOHtM70-hYIeYKk75ya7t6B6uWILhg,10971
2
+ ulid/__main__.py,sha256=TUo3dkWkJLfzRJyd55nfqGqvv1NcG7fBpsZHIi_p4rw,5421
3
+ ulid/base32.py,sha256=iUE5BGPW5Dul-Eu3ITvcymk8acNkIFAVTlrL12YIPKI,5961
4
+ ulid/constants.py,sha256=tJByGuryoTT_xfECvpMNPNwRvZMp2K_ZlZ7tOLeaXJs,335
5
+ ulid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ python_ulid-3.0.0.dist-info/METADATA,sha256=82s8fmElXLy5Um_bjqhtZfiXd54YF0TDI2iOlKliLMs,5782
7
+ python_ulid-3.0.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
8
+ python_ulid-3.0.0.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
9
+ python_ulid-3.0.0.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
10
+ python_ulid-3.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.2
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
ulid/__init__.py CHANGED
@@ -4,10 +4,10 @@ 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
10
9
  from typing import Any
10
+ from typing import cast
11
11
  from typing import Generic
12
12
  from typing import TYPE_CHECKING
13
13
  from typing import TypeVar
@@ -17,6 +17,8 @@ from ulid import constants
17
17
 
18
18
 
19
19
  if TYPE_CHECKING: # pragma: no cover
20
+ from collections.abc import Callable
21
+
20
22
  from pydantic import GetCoreSchemaHandler
21
23
  from pydantic import ValidatorFunctionWrapHandler
22
24
  from pydantic_core import CoreSchema
@@ -29,7 +31,6 @@ except ImportError: # pragma: no cover
29
31
 
30
32
  __version__ = version("python-ulid")
31
33
 
32
-
33
34
  T = TypeVar("T", bound=type)
34
35
  R = TypeVar("R")
35
36
 
@@ -44,7 +45,7 @@ class validate_type(Generic[T]): # noqa: N801
44
45
  if not isinstance(value, self.types):
45
46
  message = "Value has to be of type "
46
47
  message += " or ".join([t.__name__ for t in self.types])
47
- raise ValueError(message)
48
+ raise TypeError(message)
48
49
  return func(cls, value)
49
50
 
50
51
  return wrapped
@@ -74,23 +75,17 @@ class ULID:
74
75
 
75
76
  Args:
76
77
  value (bytes, None): A sequence of 16 bytes representing an encoded ULID.
77
- validate (bool): If set to `True` validate if the timestamp part is valid.
78
78
 
79
79
  Raises:
80
80
  ValueError: If the provided value is not a valid encoded ULID.
81
81
  """
82
82
 
83
- def __init__(self, value: bytes | None = None, validate: bool = True) -> None:
83
+ def __init__(self, value: bytes | None = None) -> None:
84
84
  if value is not None and len(value) != constants.BYTES_LEN:
85
85
  raise ValueError("ULID has to be exactly 16 bytes long.")
86
86
  self.bytes: bytes = (
87
87
  value or ULID.from_timestamp(time.time_ns() // constants.NANOSECS_IN_MILLISECS).bytes
88
88
  )
89
- if value is not None and validate:
90
- try:
91
- self.datetime # noqa: B018
92
- except ValueError as err:
93
- raise ValueError("ULID timestamp is out of range.") from err
94
89
 
95
90
  @classmethod
96
91
  @validate_type(datetime)
@@ -108,7 +103,7 @@ class ULID:
108
103
 
109
104
  @classmethod
110
105
  @validate_type(int, float)
111
- def from_timestamp(cls: type[U], value: int | float) -> U:
106
+ def from_timestamp(cls: type[U], value: float) -> U:
112
107
  """Create a new :class:`ULID`-object from a timestamp. The timestamp can be either a
113
108
  `float` representing the time in seconds (as it would be returned by :func:`time.time()`)
114
109
  or an `int` in milliseconds.
@@ -137,7 +132,7 @@ class ULID:
137
132
  >>> ULID.from_uuid(uuid4())
138
133
  ULID(27Q506DP7E9YNRXA0XVD8Z5YSG)
139
134
  """
140
- return cls(value.bytes, validate=False)
135
+ return cls(value.bytes)
141
136
 
142
137
  @classmethod
143
138
  @validate_type(bytes)
@@ -163,13 +158,45 @@ class ULID:
163
158
  """Create a new :class:`ULID`-object from an `int`."""
164
159
  return cls(int.to_bytes(value, constants.BYTES_LEN, "big"))
165
160
 
161
+ @classmethod
162
+ def parse(cls: type[U], value: Any) -> U:
163
+ """Create a new :class:`ULID`-object from a given value.
164
+
165
+ .. note:: This method should only be used when the caller is trying to parse a ULID from
166
+ a value when they're unsure what format/primitive type it will be given in.
167
+ """
168
+ if isinstance(value, ULID):
169
+ return cast(U, value)
170
+ if isinstance(value, uuid.UUID):
171
+ return cls.from_uuid(value)
172
+ if isinstance(value, str):
173
+ len_value = len(value)
174
+ if len_value == constants.UUID_REPR_LEN:
175
+ return cls.from_uuid(uuid.UUID(value))
176
+ if len_value == constants.HEX_REPR_LEN:
177
+ return cls.from_hex(value)
178
+ if len_value == constants.REPR_LEN:
179
+ return cls.from_str(value)
180
+ raise ValueError(f"Cannot parse ULID from string of length {len_value}")
181
+ if isinstance(value, int):
182
+ if len(str(value)) == constants.INT_REPR_LEN:
183
+ return cls.from_int(value)
184
+ return cls.from_timestamp(value)
185
+ if isinstance(value, float):
186
+ return cls.from_timestamp(value)
187
+ if isinstance(value, datetime):
188
+ return cls.from_datetime(value)
189
+ if isinstance(value, bytes):
190
+ return cls.from_bytes(value)
191
+ raise TypeError(f"Cannot parse ULID from type {type(value)}")
192
+
166
193
  @property
167
194
  def milliseconds(self) -> int:
168
195
  """The timestamp part as epoch time in milliseconds.
169
196
 
170
197
  Examples:
171
198
 
172
- >>> ulid.timestamp
199
+ >>> ulid.milliseconds
173
200
  1588257207560
174
201
  """
175
202
  return int.from_bytes(self.bytes[: constants.TIMESTAMP_LEN], byteorder="big")
@@ -239,22 +266,22 @@ class ULID:
239
266
  def __lt__(self, other: Any) -> bool:
240
267
  if isinstance(other, ULID):
241
268
  return self.bytes < other.bytes
242
- elif isinstance(other, int):
269
+ if isinstance(other, int):
243
270
  return int(self) < other
244
- elif isinstance(other, bytes):
271
+ if isinstance(other, bytes):
245
272
  return self.bytes < other
246
- elif isinstance(other, str):
273
+ if isinstance(other, str):
247
274
  return str(self) < other
248
275
  return NotImplemented
249
276
 
250
- def __eq__(self, other: Any) -> bool:
277
+ def __eq__(self, other: object) -> bool:
251
278
  if isinstance(other, ULID):
252
279
  return self.bytes == other.bytes
253
- elif isinstance(other, int):
280
+ if isinstance(other, int):
254
281
  return int(self) == other
255
- elif isinstance(other, bytes):
282
+ if isinstance(other, bytes):
256
283
  return self.bytes == other
257
- elif isinstance(other, str):
284
+ if isinstance(other, str):
258
285
  return str(self) == other
259
286
  return NotImplemented
260
287
 
@@ -267,14 +294,12 @@ class ULID:
267
294
 
268
295
  return core_schema.no_info_wrap_validator_function(
269
296
  cls._pydantic_validate,
270
- core_schema.union_schema(
271
- [
272
- core_schema.is_instance_schema(ULID),
273
- core_schema.no_info_plain_validator_function(ULID),
274
- core_schema.str_schema(pattern=r"[A-Z0-9]{26}", min_length=26, max_length=26),
275
- core_schema.bytes_schema(min_length=16, max_length=16),
276
- ]
277
- ),
297
+ core_schema.union_schema([
298
+ core_schema.is_instance_schema(ULID),
299
+ core_schema.no_info_plain_validator_function(ULID),
300
+ core_schema.str_schema(pattern=r"[A-Z0-9]{26}", min_length=26, max_length=26),
301
+ core_schema.bytes_schema(min_length=16, max_length=16),
302
+ ]),
278
303
  serialization=core_schema.to_string_ser_schema(
279
304
  when_used="json-unless-none",
280
305
  ),
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:
@@ -210,16 +206,17 @@ def decode_timestamp(encoded: str) -> bytes:
210
206
  raise ValueError("ULID timestamp has to be exactly 10 characters long.")
211
207
  lut = DECODE
212
208
  values: bytes = bytes(encoded, "ascii")
213
- return bytes(
214
- [
215
- ((lut[values[0]] << 5) | lut[values[1]]) & 0xFF,
216
- ((lut[values[2]] << 3) | (lut[values[3]] >> 2)) & 0xFF,
217
- ((lut[values[3]] << 6) | (lut[values[4]] << 1) | (lut[values[5]] >> 4)) & 0xFF,
218
- ((lut[values[5]] << 4) | (lut[values[6]] >> 1)) & 0xFF,
219
- ((lut[values[6]] << 7) | (lut[values[7]] << 2) | (lut[values[8]] >> 3)) & 0xFF,
220
- ((lut[values[8]] << 5) | (lut[values[9]])) & 0xFF,
221
- ]
222
- )
209
+ # https://github.com/ulid/spec?tab=readme-ov-file#overflow-errors-when-parsing-base32-strings
210
+ if lut[values[0]] > 7: # noqa: PLR2004
211
+ raise ValueError(f"Timestamp value {encoded} is too large and will overflow 128-bits.")
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
+ ])
223
220
 
224
221
 
225
222
  def decode_randomness(encoded: str) -> bytes:
@@ -227,17 +224,15 @@ def decode_randomness(encoded: str) -> bytes:
227
224
  raise ValueError("ULID randomness has to be exactly 16 characters long.")
228
225
  lut = DECODE
229
226
  values = bytes(encoded, "ascii")
230
- return bytes(
231
- [
232
- ((lut[values[0]] << 3) | (lut[values[1]] >> 2)) & 0xFF,
233
- ((lut[values[1]] << 6) | (lut[values[2]] << 1) | (lut[values[3]] >> 4)) & 0xFF,
234
- ((lut[values[3]] << 4) | (lut[values[4]] >> 1)) & 0xFF,
235
- ((lut[values[4]] << 7) | (lut[values[5]] << 2) | (lut[values[6]] >> 3)) & 0xFF,
236
- ((lut[values[6]] << 5) | (lut[values[7]])) & 0xFF,
237
- ((lut[values[8]] << 3) | (lut[values[9]] >> 2)) & 0xFF,
238
- ((lut[values[9]] << 6) | (lut[values[10]] << 1) | (lut[values[11]] >> 4)) & 0xFF,
239
- ((lut[values[11]] << 4) | (lut[values[12]] >> 1)) & 0xFF,
240
- ((lut[values[12]] << 7) | (lut[values[13]] << 2) | (lut[values[14]] >> 3)) & 0xFF,
241
- ((lut[values[14]] << 5) | (lut[values[15]])) & 0xFF,
242
- ]
243
- )
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
@@ -8,3 +8,8 @@ BYTES_LEN = TIMESTAMP_LEN + RANDOMNESS_LEN
8
8
  TIMESTAMP_REPR_LEN = 10
9
9
  RANDOMNESS_REPR_LEN = 16
10
10
  REPR_LEN = TIMESTAMP_REPR_LEN + RANDOMNESS_REPR_LEN
11
+
12
+ INT_REPR_LEN = 37
13
+
14
+ HEX_REPR_LEN = 32
15
+ UUID_REPR_LEN = 36 # UUID with dash-separated segments
@@ -1,10 +0,0 @@
1
- ulid/__init__.py,sha256=BziBUk2I6PxKvymNA0ddyUcfF9b_Zv7PKsTPV_ojixI,9929
2
- ulid/__main__.py,sha256=Cmg0NEz3GUZ2WhsZVpY3-Kq-JOkxJFaLsRNgj3Aosyw,5341
3
- ulid/base32.py,sha256=aj8pfBmuvviEJtdcGGiGImdSN_UMdVsBvIXlFkiqD1o,5963
4
- ulid/constants.py,sha256=N_nw4W2ciXO8C1IeuO3YK3j4_x9QW-0KyvrSQUuM-G4,241
5
- ulid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- python_ulid-2.6.0.dist-info/METADATA,sha256=NxDvmvrp4DzyfibtLfBPh3D605fWHRY_p709_rxwwG0,5782
7
- python_ulid-2.6.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
8
- python_ulid-2.6.0.dist-info/entry_points.txt,sha256=9214ghzfSgS8a-TjjlWDF7bI7KSaeewOCBKn5uz7bdU,50
9
- python_ulid-2.6.0.dist-info/licenses/LICENSE,sha256=DiDPNhW5yUHIqaKowvX0c1c9xWiI0xW4ji8R9N2NJbw,1056
10
- python_ulid-2.6.0.dist-info/RECORD,,