python-ulid 2.4.0.post0__tar.gz → 2.6.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.
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/CHANGELOG.rst +18 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/PKG-INFO +1 -1
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_ulid.py +33 -10
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/__init__.py +16 -2
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/base32.py +2 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.github/workflows/lint-and-test.yml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.github/workflows/publish.yml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.gitignore +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.pre-commit-config.yaml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.readthedocs.yml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/LICENSE +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/README.rst +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/Makefile +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/requirements.txt +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/api.rst +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/changelog.rst +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/conf.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/index.rst +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/hatch.toml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/logo.png +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/pyproject.toml +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/__init__.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_base32.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_cli.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/__main__.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/constants.py +0 -0
- {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/py.typed +0 -0
|
@@ -5,6 +5,22 @@ Changelog
|
|
|
5
5
|
|
|
6
6
|
Versions follow `Semantic Versioning <http://www.semver.org>`_
|
|
7
7
|
|
|
8
|
+
`2.6.0`_ - 2024-05-26
|
|
9
|
+
---------------------
|
|
10
|
+
Changed
|
|
11
|
+
~~~~~~~
|
|
12
|
+
* Provide more sophisticated validation when creating ``ULID``s from user input. When using
|
|
13
|
+
``ULID.from_str`` we will check if the characters match the base32 alphabet. In general, it is
|
|
14
|
+
ensured that the timestamp part of the ULID is not out of range.
|
|
15
|
+
|
|
16
|
+
`2.5.0`_ - 2024-04-26
|
|
17
|
+
---------------------
|
|
18
|
+
|
|
19
|
+
Changed
|
|
20
|
+
~~~~~~~
|
|
21
|
+
* Generate a more accurate JSON schema with Pydantic's ``BaseModel.model_json_schema()``. This
|
|
22
|
+
includes a specification for string and byte representations.
|
|
23
|
+
|
|
8
24
|
`2.4.0`_ - 2024-04-02
|
|
9
25
|
---------------------
|
|
10
26
|
|
|
@@ -151,6 +167,8 @@ Changed
|
|
|
151
167
|
* The package now has no external dependencies.
|
|
152
168
|
* The test-coverage has been raised to 100%.
|
|
153
169
|
|
|
170
|
+
.. _2.6.0: https://github.com/mdomke/python-ulid/compare/2.5.0...2.6.0
|
|
171
|
+
.. _2.5.0: https://github.com/mdomke/python-ulid/compare/2.4.0...2.5.0
|
|
154
172
|
.. _2.4.0: https://github.com/mdomke/python-ulid/compare/2.3.0...2.4.0
|
|
155
173
|
.. _2.3.0: https://github.com/mdomke/python-ulid/compare/2.2.0...2.3.0
|
|
156
174
|
.. _2.2.0: https://github.com/mdomke/python-ulid/compare/2.1.0...2.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-ulid
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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
|
|
@@ -5,6 +5,7 @@ from collections.abc import Callable
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from datetime import timedelta
|
|
7
7
|
from datetime import timezone
|
|
8
|
+
from typing import Optional
|
|
8
9
|
from typing import Union
|
|
9
10
|
|
|
10
11
|
import pytest
|
|
@@ -148,15 +149,17 @@ Params = Union[bytes, str, int, float]
|
|
|
148
149
|
@pytest.mark.parametrize(
|
|
149
150
|
("constructor", "value"),
|
|
150
151
|
[
|
|
151
|
-
(ULID, b"sdf"),
|
|
152
|
-
(ULID.from_timestamp, b"not-a-timestamp"),
|
|
153
|
-
(ULID.from_datetime, time.time()),
|
|
154
|
-
(ULID.from_bytes, b"not-enough"),
|
|
155
|
-
(ULID.from_bytes, 123),
|
|
156
|
-
(ULID.from_str, "not-enough"),
|
|
157
|
-
(ULID.from_str, 123),
|
|
158
|
-
(ULID.
|
|
159
|
-
(ULID.
|
|
152
|
+
(ULID, b"sdf"), # invalid length
|
|
153
|
+
(ULID.from_timestamp, b"not-a-timestamp"), # invalid type
|
|
154
|
+
(ULID.from_datetime, time.time()), # invalid type
|
|
155
|
+
(ULID.from_bytes, b"not-enough"), # invalid length
|
|
156
|
+
(ULID.from_bytes, 123), # invalid type
|
|
157
|
+
(ULID.from_str, "not-enough"), # invalid length
|
|
158
|
+
(ULID.from_str, 123), # inavlid type
|
|
159
|
+
(ULID.from_str, "notavalidulidnotavalidulid"), # invalid alphabet
|
|
160
|
+
(ULID.from_str, "Z" * 26), # invalid timestamp
|
|
161
|
+
(ULID.from_int, "not-an-int"), # invalid type
|
|
162
|
+
(ULID.from_uuid, "not-a-uuid"), # invalid type
|
|
160
163
|
],
|
|
161
164
|
)
|
|
162
165
|
def test_ulid_invalid_input(constructor: Callable[[Params], ULID], value: Params) -> None:
|
|
@@ -168,7 +171,7 @@ def test_pydantic_protocol() -> None:
|
|
|
168
171
|
ulid = ULID()
|
|
169
172
|
|
|
170
173
|
class Model(BaseModel):
|
|
171
|
-
ulid: ULID
|
|
174
|
+
ulid: Optional[ULID] = None
|
|
172
175
|
|
|
173
176
|
for value in [ulid, str(ulid), int(ulid), bytes(ulid)]:
|
|
174
177
|
model = Model(ulid=value)
|
|
@@ -188,3 +191,23 @@ def test_pydantic_protocol() -> None:
|
|
|
188
191
|
model_json = model.model_dump_json()
|
|
189
192
|
assert isinstance(json.loads(model_json)["ulid"], str)
|
|
190
193
|
assert Model.model_validate_json(model_json) == model
|
|
194
|
+
|
|
195
|
+
model_json_schema = model.model_json_schema()
|
|
196
|
+
assert "properties" in model_json_schema
|
|
197
|
+
assert "ulid" in model_json_schema["properties"]
|
|
198
|
+
assert "anyOf" in model_json_schema["properties"]["ulid"]
|
|
199
|
+
assert {
|
|
200
|
+
"maxLength": 26,
|
|
201
|
+
"minLength": 26,
|
|
202
|
+
"pattern": "[A-Z0-9]{26}",
|
|
203
|
+
"type": "string",
|
|
204
|
+
} in model_json_schema["properties"]["ulid"]["anyOf"]
|
|
205
|
+
assert {
|
|
206
|
+
"maxLength": 16,
|
|
207
|
+
"minLength": 16,
|
|
208
|
+
"type": "string",
|
|
209
|
+
"format": "binary",
|
|
210
|
+
} in model_json_schema["properties"]["ulid"]["anyOf"]
|
|
211
|
+
assert {
|
|
212
|
+
"type": "null",
|
|
213
|
+
} in model_json_schema["properties"]["ulid"]["anyOf"]
|
|
@@ -71,14 +71,26 @@ class ULID:
|
|
|
71
71
|
>>> ulid = ULID()
|
|
72
72
|
>>> str(ulid)
|
|
73
73
|
'01E75PVKXA3GFABX1M1J9NZZNF'
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
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
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If the provided value is not a valid encoded ULID.
|
|
74
81
|
"""
|
|
75
82
|
|
|
76
|
-
def __init__(self, value: bytes | None = None) -> None:
|
|
83
|
+
def __init__(self, value: bytes | None = None, validate: bool = True) -> None:
|
|
77
84
|
if value is not None and len(value) != constants.BYTES_LEN:
|
|
78
85
|
raise ValueError("ULID has to be exactly 16 bytes long.")
|
|
79
86
|
self.bytes: bytes = (
|
|
80
87
|
value or ULID.from_timestamp(time.time_ns() // constants.NANOSECS_IN_MILLISECS).bytes
|
|
81
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
|
|
82
94
|
|
|
83
95
|
@classmethod
|
|
84
96
|
@validate_type(datetime)
|
|
@@ -125,7 +137,7 @@ class ULID:
|
|
|
125
137
|
>>> ULID.from_uuid(uuid4())
|
|
126
138
|
ULID(27Q506DP7E9YNRXA0XVD8Z5YSG)
|
|
127
139
|
"""
|
|
128
|
-
return cls(value.bytes)
|
|
140
|
+
return cls(value.bytes, validate=False)
|
|
129
141
|
|
|
130
142
|
@classmethod
|
|
131
143
|
@validate_type(bytes)
|
|
@@ -259,6 +271,8 @@ class ULID:
|
|
|
259
271
|
[
|
|
260
272
|
core_schema.is_instance_schema(ULID),
|
|
261
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),
|
|
262
276
|
]
|
|
263
277
|
),
|
|
264
278
|
serialization=core_schema.to_string_ser_schema(
|
|
@@ -198,6 +198,8 @@ def encode_randomness(binary: bytes) -> str:
|
|
|
198
198
|
def decode(encoded: str) -> bytes:
|
|
199
199
|
if len(encoded) != constants.REPR_LEN:
|
|
200
200
|
raise ValueError("Encoded ULID has to be exactly 26 characters long.")
|
|
201
|
+
if any((c not in ENCODE) for c in encoded):
|
|
202
|
+
raise ValueError(f"Encoded ULID can only consist of letters in {ENCODE}.")
|
|
201
203
|
return decode_timestamp(encoded[: constants.TIMESTAMP_REPR_LEN]) + decode_randomness(
|
|
202
204
|
encoded[constants.TIMESTAMP_REPR_LEN :]
|
|
203
205
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|