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.
Files changed (27) hide show
  1. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/CHANGELOG.rst +18 -0
  2. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/PKG-INFO +1 -1
  3. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_ulid.py +33 -10
  4. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/__init__.py +16 -2
  5. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/base32.py +2 -0
  6. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.github/workflows/lint-and-test.yml +0 -0
  7. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.github/workflows/publish.yml +0 -0
  8. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.gitignore +0 -0
  9. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.pre-commit-config.yaml +0 -0
  10. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/.readthedocs.yml +0 -0
  11. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/LICENSE +0 -0
  12. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/README.rst +0 -0
  13. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/Makefile +0 -0
  14. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/requirements.txt +0 -0
  15. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/api.rst +0 -0
  16. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/changelog.rst +0 -0
  17. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/conf.py +0 -0
  18. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/docs/source/index.rst +0 -0
  19. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/hatch.toml +0 -0
  20. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/logo.png +0 -0
  21. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/pyproject.toml +0 -0
  22. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/__init__.py +0 -0
  23. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_base32.py +0 -0
  24. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/tests/test_cli.py +0 -0
  25. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/__main__.py +0 -0
  26. {python_ulid-2.4.0.post0 → python_ulid-2.6.0}/ulid/constants.py +0 -0
  27. {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.4.0.post0
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.from_int, "not-an-int"),
159
- (ULID.from_uuid, "not-a-uuid"),
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