ulid-transform 2.2.5__tar.gz → 2.2.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ulid-transform
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: Create and transform ULIDs
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -64,12 +64,20 @@ This library will use the C++ implementation from https://github.com/suyash/ulid
64
64
  '01869a2ea5fb0b43aa056293e47c0a35'
65
65
  >>> ulid_transform.ulid_now()
66
66
  '0001HZX0NW00GW0X476W5TVBFE'
67
+ >>> ulid_transform.ulid_now_bytes()
68
+ b'\x01\x9eS\x9d\x1bhl~\x1e\xf7\x959\xe1\xf2\xbe\xea'
67
69
  >>> ulid_transform.ulid_at_time(1234)
68
70
  '000000016JC62D620DGYNG2R8H'
71
+ >>> ulid_transform.ulid_at_time_bytes(1234)
72
+ b'\x00\x00\x00\x12\xd4Pq+\x1eG?\x91\xe9+|\xbd'
69
73
  >>> ulid_transform.ulid_to_bytes('0001HZX0NW00GW0X476W5TVBFE')
70
74
  b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
71
75
  >>> ulid_transform.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z")
72
76
  '01GTCKZT7K26YEVVW6AMQ3J0VT'
77
+ >>> ulid_transform.ulid_to_timestamp('0001HZX0NW00GW0X476W5TVBFE') # milliseconds since the epoch
78
+ 1677623996
79
+ >>> ulid_transform.ulid_to_timestamp(b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee') # also accepts bytes
80
+ 1677623996
73
81
  >>> ulid_transform.ulid_to_bytes_or_none('0001HZX0NW00GW0X476W5TVBFE')
74
82
  b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
75
83
  >>> ulid_transform.ulid_to_bytes_or_none(None)
@@ -40,12 +40,20 @@ This library will use the C++ implementation from https://github.com/suyash/ulid
40
40
  '01869a2ea5fb0b43aa056293e47c0a35'
41
41
  >>> ulid_transform.ulid_now()
42
42
  '0001HZX0NW00GW0X476W5TVBFE'
43
+ >>> ulid_transform.ulid_now_bytes()
44
+ b'\x01\x9eS\x9d\x1bhl~\x1e\xf7\x959\xe1\xf2\xbe\xea'
43
45
  >>> ulid_transform.ulid_at_time(1234)
44
46
  '000000016JC62D620DGYNG2R8H'
47
+ >>> ulid_transform.ulid_at_time_bytes(1234)
48
+ b'\x00\x00\x00\x12\xd4Pq+\x1eG?\x91\xe9+|\xbd'
45
49
  >>> ulid_transform.ulid_to_bytes('0001HZX0NW00GW0X476W5TVBFE')
46
50
  b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
47
51
  >>> ulid_transform.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z")
48
52
  '01GTCKZT7K26YEVVW6AMQ3J0VT'
53
+ >>> ulid_transform.ulid_to_timestamp('0001HZX0NW00GW0X476W5TVBFE') # milliseconds since the epoch
54
+ 1677623996
55
+ >>> ulid_transform.ulid_to_timestamp(b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee') # also accepts bytes
56
+ 1677623996
49
57
  >>> ulid_transform.ulid_to_bytes_or_none('0001HZX0NW00GW0X476W5TVBFE')
50
58
  b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
51
59
  >>> ulid_transform.ulid_to_bytes_or_none(None)
@@ -0,0 +1,112 @@
1
+ """Build optional C extension modules."""
2
+
3
+ from distutils.command.build_ext import build_ext
4
+ import logging
5
+ import os
6
+ from pathlib import Path
7
+ import sys
8
+ from typing import Any
9
+
10
+ try:
11
+ from setuptools import Extension, setup
12
+ except ImportError:
13
+ from distutils.core import Extension, setup
14
+
15
+ _LOGGER = logging.getLogger(__name__)
16
+
17
+
18
+ def getenv_bool(key: str, default: bool = False) -> bool:
19
+ value = os.environ.get(key, str(default)).lower()
20
+ if value in ("1", "true", "yes"):
21
+ return True
22
+ if value in ("0", "false", "no"):
23
+ return False
24
+ msg = f"Invalid value for boolean envvar {key}: {value}"
25
+ raise ValueError(msg)
26
+
27
+
28
+ ulid_module = Extension(
29
+ "ulid_transform._ulid_impl",
30
+ [
31
+ str(Path("src") / "ulid_transform" / "_ulid_impl.cpp"),
32
+ ],
33
+ language="c++",
34
+ extra_compile_args=["-std=c++11", "-O3", "-g0"],
35
+ extra_link_args=["-std=c++11"],
36
+ )
37
+
38
+
39
+ class BuildExt(build_ext):
40
+ def build_extensions(self) -> None:
41
+ if self.parallel is None: # type: ignore[has-type, unused-ignore]
42
+ self.parallel = os.cpu_count() or 1
43
+ try:
44
+ super().build_extensions()
45
+ except Exception: # nosec
46
+ _LOGGER.exception("Failed to build extensions")
47
+ if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
48
+ raise
49
+
50
+
51
+ def build(setup_kwargs: Any) -> None:
52
+ if getenv_bool("SKIP_CYTHON") or getenv_bool("SKIP_EXTENSION"):
53
+ return
54
+ try:
55
+ setup_kwargs.update(
56
+ {
57
+ "ext_modules": [ulid_module],
58
+ "cmdclass": {"build_ext": BuildExt},
59
+ }
60
+ )
61
+ except Exception:
62
+ _LOGGER.exception("Failed to configure C extension")
63
+ if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
64
+ raise
65
+
66
+
67
+ def _clean_stale_artifacts() -> None:
68
+ """Remove previously-built extension artifacts before packaging.
69
+
70
+ The wheel ``include`` globs in ``pyproject.toml`` are unconditional, so a
71
+ stale ``.so``/``.pyd`` left in ``src/ulid_transform/`` by an earlier build
72
+ would otherwise be packaged even when the extension is skipped
73
+ (``SKIP_EXTENSION``) or its build failed and was swallowed, yielding an
74
+ incompatible binary wheel instead of the requested pure-Python fallback.
75
+ Clearing them first means the include globs only ever match a freshly
76
+ built artifact from this run.
77
+ """
78
+ pkg_dir = Path("src") / "ulid_transform"
79
+ for pattern in ("*.so", "*.pyd"):
80
+ for artifact in pkg_dir.glob(pattern):
81
+ artifact.unlink()
82
+
83
+
84
+ def _run_main() -> None:
85
+ """Build the C extension when invoked directly.
86
+
87
+ poetry-core calls ``python build_ext.py`` (no args) during the wheel
88
+ build when ``generate-setup-file = false`` is set in
89
+ ``pyproject.toml``. We synthesise a ``setuptools.setup()`` call with
90
+ an in-place ``build_ext`` so the compiled extension lands next to its
91
+ sources in ``src/ulid_transform/`` where poetry-core's
92
+ ``find_files_to_add`` picks it up. ``generate-setup-file = false``
93
+ is set so the sdist does not ship a generated ``setup.py`` that does
94
+ ``from build_ext import *``, which fails under
95
+ ``PYTHONSAFEPATH=1`` (see issue #137).
96
+ """
97
+ _clean_stale_artifacts()
98
+ setup_kwargs: dict[str, Any] = {
99
+ "name": "ulid-transform",
100
+ "packages": ["ulid_transform"],
101
+ "package_dir": {"": "src"},
102
+ }
103
+ build(setup_kwargs)
104
+ if "ext_modules" not in setup_kwargs:
105
+ return
106
+ if len(sys.argv) == 1:
107
+ sys.argv.extend(["build_ext", "--inplace"])
108
+ setup(**setup_kwargs)
109
+
110
+
111
+ if __name__ == "__main__":
112
+ _run_main()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ulid-transform"
3
- version = "2.2.5"
3
+ version = "2.2.7"
4
4
  license = "MIT"
5
5
  description = "Create and transform ULIDs"
6
6
  readme = "README.md"
@@ -23,9 +23,13 @@ classifiers = [
23
23
  packages = [
24
24
  { include = "ulid_transform", from = "src" },
25
25
  ]
26
+ include = [
27
+ { path = "src/ulid_transform/*.so", format = "wheel" },
28
+ { path = "src/ulid_transform/*.pyd", format = "wheel" },
29
+ ]
26
30
 
27
31
  [tool.poetry.build]
28
- generate-setup-file = true
32
+ generate-setup-file = false
29
33
  script = "build_ext.py"
30
34
 
31
35
  [tool.poetry.dependencies]
@@ -1,4 +1,4 @@
1
- __version__ = "2.2.5"
1
+ __version__ = "2.2.7"
2
2
 
3
3
  try:
4
4
  from ._ulid_impl import (
@@ -189,7 +189,11 @@ py_ulid_to_bytes(PyObject* module, PyObject* arg)
189
189
  const char* str = PyUnicode_AsUTF8AndSize(arg, &len);
190
190
  if (!str)
191
191
  return NULL;
192
- if (len != ULID_TEXT_LEN) {
192
+ // len is the UTF-8 byte length. A non-ASCII string can still be exactly
193
+ // 26 bytes (e.g. 13 two-byte codepoints), so an ASCII check is required to
194
+ // reject it — otherwise DecodeBase32To reads garbage. Mirrors the Python
195
+ // impl's `len(value) != 26 or not value.isascii()`.
196
+ if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg)) {
193
197
  PyErr_Format(PyExc_ValueError,
194
198
  "ULID must be a 26 character string: %R", arg);
195
199
  return NULL;
@@ -229,7 +233,9 @@ py_ulid_to_bytes_or_none(PyObject* module, PyObject* arg)
229
233
  const char* str = PyUnicode_AsUTF8AndSize(arg, &len);
230
234
  if (!str)
231
235
  return NULL;
232
- if (len != ULID_TEXT_LEN)
236
+ // See py_ulid_to_bytes: a non-ASCII string can be exactly 26 UTF-8 bytes,
237
+ // so reject anything non-ASCII to match the Python impl returning None.
238
+ if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg))
233
239
  Py_RETURN_NONE;
234
240
  uint8_t buf[ULID_BYTES_LEN];
235
241
  ulid::DecodeBase32To(str, buf);
@@ -271,7 +277,9 @@ py_ulid_to_timestamp(PyObject* module, PyObject* arg)
271
277
  const char* str = PyUnicode_AsUTF8AndSize(arg, &len);
272
278
  if (!str)
273
279
  return NULL;
274
- if (len != ULID_TEXT_LEN) {
280
+ // See py_ulid_to_bytes: a non-ASCII string can be exactly 26 UTF-8 bytes,
281
+ // so reject anything non-ASCII to match the Python impl raising ValueError.
282
+ if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg)) {
275
283
  PyErr_Format(PyExc_ValueError,
276
284
  "ULID must be a 26 character string: %R", arg);
277
285
  return NULL;
@@ -1,63 +0,0 @@
1
- """Build optional C extension modules."""
2
-
3
- from distutils.command.build_ext import build_ext
4
- import logging
5
- import os
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- try:
10
- from setuptools import Extension
11
- except ImportError:
12
- from distutils.core import Extension
13
-
14
- _LOGGER = logging.getLogger(__name__)
15
-
16
-
17
- def getenv_bool(key: str, default: bool = False) -> bool:
18
- value = os.environ.get(key, str(default)).lower()
19
- if value in ("1", "true", "yes"):
20
- return True
21
- if value in ("0", "false", "no"):
22
- return False
23
- msg = f"Invalid value for boolean envvar {key}: {value}"
24
- raise ValueError(msg)
25
-
26
-
27
- ulid_module = Extension(
28
- "ulid_transform._ulid_impl",
29
- [
30
- str(Path("src") / "ulid_transform" / "_ulid_impl.cpp"),
31
- ],
32
- language="c++",
33
- extra_compile_args=["-std=c++11", "-O3", "-g0"],
34
- extra_link_args=["-std=c++11"],
35
- )
36
-
37
-
38
- class BuildExt(build_ext):
39
- def build_extensions(self) -> None:
40
- if self.parallel is None: # type: ignore[has-type, unused-ignore]
41
- self.parallel = os.cpu_count() or 1
42
- try:
43
- super().build_extensions()
44
- except Exception: # nosec
45
- _LOGGER.exception("Failed to build extensions")
46
- if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
47
- raise
48
-
49
-
50
- def build(setup_kwargs: Any) -> None:
51
- if getenv_bool("SKIP_CYTHON") or getenv_bool("SKIP_EXTENSION"):
52
- return
53
- try:
54
- setup_kwargs.update(
55
- {
56
- "ext_modules": [ulid_module],
57
- "cmdclass": {"build_ext": BuildExt},
58
- }
59
- )
60
- except Exception:
61
- _LOGGER.exception("Failed to configure C extension")
62
- if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
63
- raise
@@ -1,31 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- package_dir = \
5
- {'': 'src'}
6
-
7
- packages = \
8
- ['ulid_transform']
9
-
10
- package_data = \
11
- {'': ['*']}
12
-
13
- setup_kwargs = {
14
- 'name': 'ulid-transform',
15
- 'version': '2.2.5',
16
- 'description': 'Create and transform ULIDs',
17
- 'long_description': '# Fast ULID transformations\n\n<p align="center">\n <a href="https://github.com/bluetooth-devices/ulid-transform/actions/workflows/ci.yml?query=branch%3Amain">\n <img src="https://img.shields.io/github/actions/workflow/status/bluetooth-devices/ulid-transform/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >\n </a>\n <a href="https://codecov.io/gh/bluetooth-devices/ulid-transform">\n <img src="https://img.shields.io/codecov/c/github/bluetooth-devices/ulid-transform.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">\n </a>\n</p>\n<p align="center">\n <a href="https://python-poetry.org/">\n <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">\n </a>\n <a href="https://github.com/ambv/black">\n <img src="https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square" alt="black">\n </a>\n <a href="https://github.com/pre-commit/pre-commit">\n <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">\n </a>\n <a href="https://codspeed.io/Bluetooth-Devices/ulid-transform"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed Badge"/></a>\n</p>\n<p align="center">\n <a href="https://pypi.org/project/ulid-transform/">\n <img src="https://img.shields.io/pypi/v/ulid-transform.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">\n </a>\n <img src="https://img.shields.io/pypi/pyversions/ulid-transform.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">\n <img src="https://img.shields.io/pypi/l/ulid-transform.svg?style=flat-square" alt="License">\n</p>\n\nCreate and transform ULIDs\n\nThis library will use the C++ implementation from https://github.com/suyash/ulid when the C extension is available, and will fallback to pure python if it is not.\n\n## Example\n\n```python\n>>> import ulid_transform\n>>> ulid_transform.ulid_hex()\n\'01869a2ea5fb0b43aa056293e47c0a35\'\n>>> ulid_transform.ulid_now()\n\'0001HZX0NW00GW0X476W5TVBFE\'\n>>> ulid_transform.ulid_at_time(1234)\n\'000000016JC62D620DGYNG2R8H\'\n>>> ulid_transform.ulid_to_bytes(\'0001HZX0NW00GW0X476W5TVBFE\')\nb\'\\x00\\x00c\\xfe\\x82\\xbc\\x00!\\xc0t\\x877\\x0b\\xad\\xad\\xee\'\n>>> ulid_transform.bytes_to_ulid(b"\\x01\\x86\\x99?\\xe8\\xf3\\x11\\xbc\\xed\\xef\\x86U.9\\x03z")\n\'01GTCKZT7K26YEVVW6AMQ3J0VT\'\n>>> ulid_transform.ulid_to_bytes_or_none(\'0001HZX0NW00GW0X476W5TVBFE\')\nb\'\\x00\\x00c\\xfe\\x82\\xbc\\x00!\\xc0t\\x877\\x0b\\xad\\xad\\xee\'\n>>> ulid_transform.ulid_to_bytes_or_none(None)\n>>> ulid_transform.bytes_to_ulid_or_none(b\'\\x00\\x00c\\xfe\\x82\\xbc\\x00!\\xc0t\\x877\\x0b\\xad\\xad\\xee\')\n\'0001HZX0NW00GW0X476W5TVBFE\'\n>>> ulid_transform.bytes_to_ulid_or_none(None)\n```\n\n## Installation\n\nInstall this via pip (or your favourite package manager):\n\n`pip install ulid-transform`\n\n## Contributors ✨\n\nThanks to https://github.com/suyash/ulid which provides the C++ implementation guts.\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- prettier-ignore-start -->\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- markdownlint-disable -->\n<!-- markdownlint-enable -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n<!-- prettier-ignore-end -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n\n## Credits\n\nThis package was created with\n[Copier](https://copier.readthedocs.io/) and the\n[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)\nproject template.\n',
18
- 'author': 'J. Nick Koston',
19
- 'author_email': 'nick@koston.org',
20
- 'maintainer': 'None',
21
- 'maintainer_email': 'None',
22
- 'url': 'https://github.com/bluetooth-devices/ulid-transform',
23
- 'package_dir': package_dir,
24
- 'packages': packages,
25
- 'package_data': package_data,
26
- 'python_requires': '>=3.11',
27
- }
28
- from build_ext import *
29
- build(setup_kwargs)
30
-
31
- setup(**setup_kwargs)
File without changes