ulid-transform 2.2.4__tar.gz → 2.2.6__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.
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/PKG-INFO +2 -2
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/README.md +1 -1
- ulid_transform-2.2.6/build_ext.py +112 -0
- ulid_transform-2.2.6/pyproject.toml +177 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/__init__.py +1 -1
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/_py_ulid_impl.py +23 -7
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/_ulid_impl.cpp +45 -2
- ulid_transform-2.2.4/build_ext.py +0 -60
- ulid_transform-2.2.4/pyproject.toml +0 -108
- ulid_transform-2.2.4/setup.py +0 -31
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/LICENSE +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/__init__.pyi +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/py.typed +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/splitmix64.hh +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/ulid_base32.hh +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/ulid_struct.hh +0 -0
- {ulid_transform-2.2.4 → ulid_transform-2.2.6}/src/ulid_transform/ulid_uint128.hh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ulid-transform
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6
|
|
4
4
|
Summary: Create and transform ULIDs
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -68,7 +68,7 @@ This library will use the C++ implementation from https://github.com/suyash/ulid
|
|
|
68
68
|
'000000016JC62D620DGYNG2R8H'
|
|
69
69
|
>>> ulid_transform.ulid_to_bytes('0001HZX0NW00GW0X476W5TVBFE')
|
|
70
70
|
b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
|
|
71
|
-
|
|
71
|
+
>>> ulid_transform.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z")
|
|
72
72
|
'01GTCKZT7K26YEVVW6AMQ3J0VT'
|
|
73
73
|
>>> ulid_transform.ulid_to_bytes_or_none('0001HZX0NW00GW0X476W5TVBFE')
|
|
74
74
|
b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
|
|
@@ -44,7 +44,7 @@ This library will use the C++ implementation from https://github.com/suyash/ulid
|
|
|
44
44
|
'000000016JC62D620DGYNG2R8H'
|
|
45
45
|
>>> ulid_transform.ulid_to_bytes('0001HZX0NW00GW0X476W5TVBFE')
|
|
46
46
|
b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
|
|
47
|
-
|
|
47
|
+
>>> ulid_transform.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z")
|
|
48
48
|
'01GTCKZT7K26YEVVW6AMQ3J0VT'
|
|
49
49
|
>>> ulid_transform.ulid_to_bytes_or_none('0001HZX0NW00GW0X476W5TVBFE')
|
|
50
50
|
b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee'
|
|
@@ -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()
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ulid-transform"
|
|
3
|
+
version = "2.2.6"
|
|
4
|
+
license = "MIT"
|
|
5
|
+
description = "Create and transform ULIDs"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [{ name = "J. Nick Koston", email = "nick@koston.org" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
|
|
10
|
+
[project.urls]
|
|
11
|
+
"Repository" = "https://github.com/bluetooth-devices/ulid-transform"
|
|
12
|
+
"Bug Tracker" = "https://github.com/bluetooth-devices/ulid-transform/issues"
|
|
13
|
+
"Changelog" = "https://github.com/bluetooth-devices/ulid-transform/blob/main/CHANGELOG.md"
|
|
14
|
+
|
|
15
|
+
[tool.poetry]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Natural Language :: English",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
]
|
|
23
|
+
packages = [
|
|
24
|
+
{ include = "ulid_transform", from = "src" },
|
|
25
|
+
]
|
|
26
|
+
include = [
|
|
27
|
+
{ path = "src/ulid_transform/*.so", format = "wheel" },
|
|
28
|
+
{ path = "src/ulid_transform/*.pyd", format = "wheel" },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.poetry.build]
|
|
32
|
+
generate-setup-file = false
|
|
33
|
+
script = "build_ext.py"
|
|
34
|
+
|
|
35
|
+
[tool.poetry.dependencies]
|
|
36
|
+
python = "^3.11"
|
|
37
|
+
|
|
38
|
+
[tool.poetry.group.dev.dependencies]
|
|
39
|
+
pytest = ">=9.0.3,<10"
|
|
40
|
+
pytest-cov = ">=3,<8"
|
|
41
|
+
setuptools = ">=65.4.1,<83.0.0"
|
|
42
|
+
pytest-codspeed = ">=5.0.2,<6.0.0"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[tool.poetry.group.benchmark.dependencies]
|
|
46
|
+
ulid-py = "^1.1.0"
|
|
47
|
+
ulid2 = "^0.3.0"
|
|
48
|
+
pytest-benchmark = ">=4,<6"
|
|
49
|
+
|
|
50
|
+
[tool.semantic_release]
|
|
51
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
52
|
+
version_variables = [
|
|
53
|
+
"src/ulid_transform/__init__.py:__version__",
|
|
54
|
+
]
|
|
55
|
+
build_command = "pip install poetry && poetry build"
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
pythonpath = ["src"]
|
|
59
|
+
|
|
60
|
+
[tool.coverage.run]
|
|
61
|
+
branch = true
|
|
62
|
+
|
|
63
|
+
[tool.coverage.report]
|
|
64
|
+
exclude_lines = [
|
|
65
|
+
"pragma: no cover",
|
|
66
|
+
"@overload",
|
|
67
|
+
"if TYPE_CHECKING",
|
|
68
|
+
"raise NotImplementedError",
|
|
69
|
+
'if __name__ == "__main__":',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.mypy]
|
|
73
|
+
check_untyped_defs = true
|
|
74
|
+
disallow_any_generics = true
|
|
75
|
+
disallow_incomplete_defs = true
|
|
76
|
+
disallow_untyped_defs = true
|
|
77
|
+
mypy_path = "src/"
|
|
78
|
+
no_implicit_optional = true
|
|
79
|
+
show_error_codes = true
|
|
80
|
+
warn_unreachable = true
|
|
81
|
+
warn_unused_ignores = true
|
|
82
|
+
exclude = [
|
|
83
|
+
'setup.py',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[[tool.mypy.overrides]]
|
|
87
|
+
module = "tests.*"
|
|
88
|
+
allow_untyped_defs = true
|
|
89
|
+
warn_unused_ignores = false
|
|
90
|
+
|
|
91
|
+
[[tool.mypy.overrides]]
|
|
92
|
+
module = "bench.*"
|
|
93
|
+
allow_untyped_defs = true
|
|
94
|
+
|
|
95
|
+
[tool.ruff]
|
|
96
|
+
required-version = ">=0.5.0"
|
|
97
|
+
target-version = "py311"
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint]
|
|
100
|
+
select = [
|
|
101
|
+
"A", # flake8-builtins
|
|
102
|
+
"ARG", # flake8-unused-arguments
|
|
103
|
+
"ASYNC", # async rules
|
|
104
|
+
"B", # flake8-bugbear
|
|
105
|
+
"BLE", # flake8-blind-except
|
|
106
|
+
"C4", # flake8-comprehensions
|
|
107
|
+
"C90", # mccabe complexity
|
|
108
|
+
"DTZ", # flake8-datetimez
|
|
109
|
+
"E", # pycodestyle
|
|
110
|
+
"EM", # flake8-errmsg
|
|
111
|
+
"ERA", # eradicate (commented-out code)
|
|
112
|
+
"EXE", # flake8-executable
|
|
113
|
+
"F", # pyflakes/autoflake
|
|
114
|
+
"FA", # flake8-future-annotations
|
|
115
|
+
"FIX", # flake8-fixme
|
|
116
|
+
"FLY", # flynt
|
|
117
|
+
"FURB", # refurb
|
|
118
|
+
"G", # flake8-logging-format
|
|
119
|
+
"I", # isort
|
|
120
|
+
"ICN", # flake8-import-conventions
|
|
121
|
+
"INP", # flake8-no-pep420 (implicit namespace packages)
|
|
122
|
+
"ISC", # flake8-implicit-str-concat
|
|
123
|
+
"LOG", # flake8-logging
|
|
124
|
+
"N", # pep8-naming
|
|
125
|
+
"NPY", # numpy-specific rules
|
|
126
|
+
"PERF", # Perflint
|
|
127
|
+
"PGH", # pygrep-hooks
|
|
128
|
+
"PIE", # flake8-pie
|
|
129
|
+
"PL", # pylint
|
|
130
|
+
"PT", # flake8-pytest-style
|
|
131
|
+
"PTH", # flake8-use-pathlib
|
|
132
|
+
"PYI", # flake8-pyi
|
|
133
|
+
"Q", # flake8-quotes
|
|
134
|
+
"UP", # pyupgrade
|
|
135
|
+
"RET", # flake8-return
|
|
136
|
+
"RSE", # flake8-raise
|
|
137
|
+
"RUF", # ruff
|
|
138
|
+
"S", # flake8-bandit
|
|
139
|
+
"SIM", # flake8-SIM
|
|
140
|
+
"SLF", # flake8-self
|
|
141
|
+
"SLOT", # flake8-slots
|
|
142
|
+
"T10", # flake8-debugger
|
|
143
|
+
"T20", # flake8-print
|
|
144
|
+
"TC", # flake8-type-checking
|
|
145
|
+
"TD", # flake8-todos
|
|
146
|
+
"TID", # Tidy imports
|
|
147
|
+
"TRY", # try rules
|
|
148
|
+
"W", # pycodestyle warnings
|
|
149
|
+
"YTT", # flake8-2020
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
ignore = [
|
|
153
|
+
"ASYNC109", # `timeout` parameters are part of the public async API
|
|
154
|
+
"E501", # line too long
|
|
155
|
+
"PLR0911", # Too many return statements
|
|
156
|
+
"PLR0912", # Too many branches
|
|
157
|
+
"PLR0913", # Too many arguments to function call
|
|
158
|
+
"PLR0915", # Too many statements
|
|
159
|
+
"PLR2004", # Magic value used in comparison
|
|
160
|
+
"PLW2901", # Outer variable overwritten by inner target
|
|
161
|
+
"TRY003", # Avoid specifying long messages outside the exception class
|
|
162
|
+
"TID252", # Prefer absolute imports over relative imports from parent modules
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
[tool.ruff.lint.isort]
|
|
166
|
+
force-sort-within-sections = true
|
|
167
|
+
known-first-party = ["ulid_transform", "tests"]
|
|
168
|
+
combine-as-imports = true
|
|
169
|
+
split-on-trailing-comma = false
|
|
170
|
+
|
|
171
|
+
[tool.ruff.lint.per-file-ignores]
|
|
172
|
+
"bench/*" = ["S101", "SLF", "T20"]
|
|
173
|
+
"tests/*" = ["ARG", "S101", "S106", "S311", "SLF"]
|
|
174
|
+
|
|
175
|
+
[build-system]
|
|
176
|
+
requires = ['setuptools>=77.0', "poetry-core>=2.1.0"]
|
|
177
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -287,6 +287,9 @@ def ulid_at_time_bytes(timestamp: float) -> bytes:
|
|
|
287
287
|
|
|
288
288
|
uuid.UUID(bytes=ulid_bytes)
|
|
289
289
|
"""
|
|
290
|
+
if not isinstance(timestamp, (int, float)):
|
|
291
|
+
msg = f"must be real number, not {type(timestamp).__name__}" # type: ignore[unreachable]
|
|
292
|
+
raise TypeError(msg)
|
|
290
293
|
return int(timestamp * 1000).to_bytes(6, byteorder="big") + int(
|
|
291
294
|
getrandbits(80)
|
|
292
295
|
).to_bytes(10, byteorder="big")
|
|
@@ -363,9 +366,18 @@ def _encode(ulid_bytes: bytes) -> str:
|
|
|
363
366
|
def ulid_to_bytes(value: str) -> bytes:
|
|
364
367
|
"""Decode a ulid to bytes."""
|
|
365
368
|
if not isinstance(value, str):
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
+
msg = f"ULID must be a string, not {type(value).__name__}" # type: ignore[unreachable]
|
|
370
|
+
raise TypeError(msg)
|
|
371
|
+
if len(value) != 26 or not value.isascii():
|
|
372
|
+
# The C extension measures length in UTF-8 bytes via
|
|
373
|
+
# PyUnicode_AsUTF8AndSize, so any non-ASCII codepoint pushes the byte
|
|
374
|
+
# length past 26 and is rejected with ValueError there. Match that: a
|
|
375
|
+
# non-ASCII string is never a valid 26-character ULID, and folding the
|
|
376
|
+
# check here keeps the exception type aligned (C raises ValueError; the
|
|
377
|
+
# bare value.encode("ascii") below would otherwise raise
|
|
378
|
+
# UnicodeEncodeError).
|
|
379
|
+
msg = f"ULID must be a 26 character string: {value}"
|
|
380
|
+
raise ValueError(msg)
|
|
369
381
|
encoded = value.encode("ascii")
|
|
370
382
|
decoding = _DECODE
|
|
371
383
|
return bytes(
|
|
@@ -423,9 +435,11 @@ def ulid_to_bytes(value: str) -> bytes:
|
|
|
423
435
|
def bytes_to_ulid(value: bytes) -> str:
|
|
424
436
|
"""Encode bytes to a ulid."""
|
|
425
437
|
if not isinstance(value, bytes):
|
|
426
|
-
|
|
438
|
+
msg = f"ULID bytes must be bytes, not {type(value).__name__}" # type: ignore[unreachable]
|
|
439
|
+
raise TypeError(msg)
|
|
427
440
|
if len(value) != 16:
|
|
428
|
-
|
|
441
|
+
msg = f"ULID bytes must be 16 bytes: {value!r}"
|
|
442
|
+
raise ValueError(msg)
|
|
429
443
|
return _encode(value)
|
|
430
444
|
|
|
431
445
|
|
|
@@ -453,10 +467,12 @@ def ulid_to_timestamp(ulid: str | bytes) -> int:
|
|
|
453
467
|
"""
|
|
454
468
|
if isinstance(ulid, bytes):
|
|
455
469
|
if len(ulid) != 16:
|
|
456
|
-
|
|
470
|
+
msg = f"ULID bytes must be 16 bytes: {ulid!r}"
|
|
471
|
+
raise ValueError(msg)
|
|
457
472
|
ulid_bytes = ulid
|
|
458
473
|
elif isinstance(ulid, str):
|
|
459
474
|
ulid_bytes = ulid_to_bytes(ulid)
|
|
460
475
|
else:
|
|
461
|
-
|
|
476
|
+
msg = f"ULID must be a string or bytes, not {type(ulid).__name__}" # type: ignore[unreachable]
|
|
477
|
+
raise TypeError(msg)
|
|
462
478
|
return int.from_bytes(b"\x00\x00" + ulid_bytes[:6], "big")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "Python.h"
|
|
2
2
|
|
|
3
|
+
#include <cmath>
|
|
3
4
|
#include <string.h>
|
|
4
5
|
|
|
5
6
|
#ifdef __SIZEOF_INT128__
|
|
@@ -90,6 +91,42 @@ py_ulid_now_bytes(PyObject* module, PyObject* Py_UNUSED(ignored))
|
|
|
90
91
|
return PyBytes_FromStringAndSize((const char*)buf, ULID_BYTES_LEN);
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/* Validate a timestamp (in seconds) and convert to milliseconds.
|
|
95
|
+
* Returns 0 on success, -1 on error (with a Python exception set).
|
|
96
|
+
* Error types/messages mirror what int(ts * 1000).to_bytes(6, 'big') raises
|
|
97
|
+
* in the Python implementation, so the two impls have exception-type parity.
|
|
98
|
+
*/
|
|
99
|
+
static inline int
|
|
100
|
+
validate_timestamp_ms(double ts, int64_t* out_ms)
|
|
101
|
+
{
|
|
102
|
+
if (std::isnan(ts)) {
|
|
103
|
+
PyErr_SetString(PyExc_ValueError,
|
|
104
|
+
"cannot convert float NaN to integer");
|
|
105
|
+
return -1;
|
|
106
|
+
}
|
|
107
|
+
if (std::isinf(ts)) {
|
|
108
|
+
PyErr_SetString(PyExc_OverflowError,
|
|
109
|
+
"cannot convert float infinity to integer");
|
|
110
|
+
return -1;
|
|
111
|
+
}
|
|
112
|
+
double ts_ms = ts * 1000.0;
|
|
113
|
+
if (ts_ms < 0.0) {
|
|
114
|
+
PyErr_SetString(PyExc_OverflowError,
|
|
115
|
+
"can't convert negative int to unsigned");
|
|
116
|
+
return -1;
|
|
117
|
+
}
|
|
118
|
+
// ULID timestamps are 48-bit unsigned milliseconds.
|
|
119
|
+
// 2^48 (281474976710656) is the exclusive upper bound and is exactly
|
|
120
|
+
// representable as a double, so the comparison is precise here.
|
|
121
|
+
if (ts_ms >= 281474976710656.0) {
|
|
122
|
+
PyErr_SetString(PyExc_OverflowError,
|
|
123
|
+
"int too big to convert");
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
*out_ms = static_cast<int64_t>(ts_ms);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
93
130
|
/* ulid_at_time_bytes(timestamp) -> bytes */
|
|
94
131
|
static PyObject*
|
|
95
132
|
py_ulid_at_time_bytes(PyObject* module, PyObject* arg)
|
|
@@ -97,8 +134,11 @@ py_ulid_at_time_bytes(PyObject* module, PyObject* arg)
|
|
|
97
134
|
double ts = PyFloat_AsDouble(arg);
|
|
98
135
|
if (ts == -1.0 && PyErr_Occurred())
|
|
99
136
|
return NULL;
|
|
137
|
+
int64_t ts_ms;
|
|
138
|
+
if (validate_timestamp_ms(ts, &ts_ms) < 0)
|
|
139
|
+
return NULL;
|
|
100
140
|
ulid::ULID ulid;
|
|
101
|
-
ulid::EncodeTimestamp(
|
|
141
|
+
ulid::EncodeTimestamp(ts_ms, ulid);
|
|
102
142
|
ulid::EncodeEntropyFast(ulid);
|
|
103
143
|
uint8_t buf[ULID_BYTES_LEN];
|
|
104
144
|
ulid::MarshalBinaryTo(ulid, buf);
|
|
@@ -124,8 +164,11 @@ py_ulid_at_time(PyObject* module, PyObject* arg)
|
|
|
124
164
|
double ts = PyFloat_AsDouble(arg);
|
|
125
165
|
if (ts == -1.0 && PyErr_Occurred())
|
|
126
166
|
return NULL;
|
|
167
|
+
int64_t ts_ms;
|
|
168
|
+
if (validate_timestamp_ms(ts, &ts_ms) < 0)
|
|
169
|
+
return NULL;
|
|
127
170
|
ulid::ULID ulid;
|
|
128
|
-
ulid::EncodeTimestamp(
|
|
171
|
+
ulid::EncodeTimestamp(ts_ms, ulid);
|
|
129
172
|
ulid::EncodeEntropyFast(ulid);
|
|
130
173
|
char buf[ULID_TEXT_LEN];
|
|
131
174
|
ulid::MarshalTo(ulid, buf);
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"""Build optional C extension modules."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
from distutils.command.build_ext import build_ext
|
|
6
|
-
from os.path import join
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
try:
|
|
10
|
-
from setuptools import Extension
|
|
11
|
-
except ImportError:
|
|
12
|
-
from distutils.core import Extension
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def getenv_bool(key: str, default: bool = False) -> bool:
|
|
16
|
-
value = os.environ.get(key, str(default)).lower()
|
|
17
|
-
if value in ("1", "true", "yes"):
|
|
18
|
-
return True
|
|
19
|
-
if value in ("0", "false", "no"):
|
|
20
|
-
return False
|
|
21
|
-
raise ValueError(f"Invalid value for boolean envvar {key}: {value}")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ulid_module = Extension(
|
|
25
|
-
"ulid_transform._ulid_impl",
|
|
26
|
-
[
|
|
27
|
-
join("src", "ulid_transform", "_ulid_impl.cpp"),
|
|
28
|
-
],
|
|
29
|
-
language="c++",
|
|
30
|
-
extra_compile_args=["-std=c++11", "-O3", "-g0"],
|
|
31
|
-
extra_link_args=["-std=c++11"],
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class BuildExt(build_ext):
|
|
36
|
-
def build_extensions(self) -> None:
|
|
37
|
-
if self.parallel is None: # type: ignore[has-type, unused-ignore]
|
|
38
|
-
self.parallel = os.cpu_count() or 1
|
|
39
|
-
try:
|
|
40
|
-
super().build_extensions()
|
|
41
|
-
except Exception: # nosec
|
|
42
|
-
logging.exception("Failed to build extensions")
|
|
43
|
-
if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
|
|
44
|
-
raise
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def build(setup_kwargs: Any) -> None:
|
|
48
|
-
if getenv_bool("SKIP_CYTHON") or getenv_bool("SKIP_EXTENSION"):
|
|
49
|
-
return
|
|
50
|
-
try:
|
|
51
|
-
setup_kwargs.update(
|
|
52
|
-
{
|
|
53
|
-
"ext_modules": [ulid_module],
|
|
54
|
-
"cmdclass": {"build_ext": BuildExt},
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
except Exception:
|
|
58
|
-
logging.exception("Failed to configure C extension")
|
|
59
|
-
if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"):
|
|
60
|
-
raise
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "ulid-transform"
|
|
3
|
-
version = "2.2.4"
|
|
4
|
-
license = "MIT"
|
|
5
|
-
description = "Create and transform ULIDs"
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
authors = [{ name = "J. Nick Koston", email = "nick@koston.org" }]
|
|
8
|
-
requires-python = ">=3.11"
|
|
9
|
-
|
|
10
|
-
[project.urls]
|
|
11
|
-
"Repository" = "https://github.com/bluetooth-devices/ulid-transform"
|
|
12
|
-
"Bug Tracker" = "https://github.com/bluetooth-devices/ulid-transform/issues"
|
|
13
|
-
"Changelog" = "https://github.com/bluetooth-devices/ulid-transform/blob/main/CHANGELOG.md"
|
|
14
|
-
|
|
15
|
-
[tool.poetry]
|
|
16
|
-
classifiers = [
|
|
17
|
-
"Development Status :: 5 - Production/Stable",
|
|
18
|
-
"Intended Audience :: Developers",
|
|
19
|
-
"Natural Language :: English",
|
|
20
|
-
"Operating System :: OS Independent",
|
|
21
|
-
"Topic :: Software Development :: Libraries",
|
|
22
|
-
]
|
|
23
|
-
packages = [
|
|
24
|
-
{ include = "ulid_transform", from = "src" },
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
[tool.poetry.build]
|
|
28
|
-
generate-setup-file = true
|
|
29
|
-
script = "build_ext.py"
|
|
30
|
-
|
|
31
|
-
[tool.poetry.dependencies]
|
|
32
|
-
python = "^3.11"
|
|
33
|
-
|
|
34
|
-
[tool.poetry.group.dev.dependencies]
|
|
35
|
-
pytest = ">=9.0.3,<10"
|
|
36
|
-
pytest-cov = ">=3,<8"
|
|
37
|
-
setuptools = ">=65.4.1,<83.0.0"
|
|
38
|
-
pytest-codspeed = ">=5.0.2,<6.0.0"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
[tool.poetry.group.benchmark.dependencies]
|
|
42
|
-
ulid-py = "^1.1.0"
|
|
43
|
-
ulid2 = "^0.3.0"
|
|
44
|
-
pytest-benchmark = ">=4,<6"
|
|
45
|
-
|
|
46
|
-
[tool.semantic_release]
|
|
47
|
-
version_toml = ["pyproject.toml:project.version"]
|
|
48
|
-
version_variables = [
|
|
49
|
-
"src/ulid_transform/__init__.py:__version__",
|
|
50
|
-
]
|
|
51
|
-
build_command = "pip install poetry && poetry build"
|
|
52
|
-
|
|
53
|
-
[tool.pytest.ini_options]
|
|
54
|
-
pythonpath = ["src"]
|
|
55
|
-
|
|
56
|
-
[tool.coverage.run]
|
|
57
|
-
branch = true
|
|
58
|
-
|
|
59
|
-
[tool.coverage.report]
|
|
60
|
-
exclude_lines = [
|
|
61
|
-
"pragma: no cover",
|
|
62
|
-
"@overload",
|
|
63
|
-
"if TYPE_CHECKING",
|
|
64
|
-
"raise NotImplementedError",
|
|
65
|
-
'if __name__ == "__main__":',
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
[tool.mypy]
|
|
69
|
-
check_untyped_defs = true
|
|
70
|
-
disallow_any_generics = true
|
|
71
|
-
disallow_incomplete_defs = true
|
|
72
|
-
disallow_untyped_defs = true
|
|
73
|
-
mypy_path = "src/"
|
|
74
|
-
no_implicit_optional = true
|
|
75
|
-
show_error_codes = true
|
|
76
|
-
warn_unreachable = true
|
|
77
|
-
warn_unused_ignores = true
|
|
78
|
-
exclude = [
|
|
79
|
-
'setup.py',
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
[[tool.mypy.overrides]]
|
|
83
|
-
module = "tests.*"
|
|
84
|
-
allow_untyped_defs = true
|
|
85
|
-
warn_unused_ignores = false
|
|
86
|
-
|
|
87
|
-
[[tool.mypy.overrides]]
|
|
88
|
-
module = "bench.*"
|
|
89
|
-
allow_untyped_defs = true
|
|
90
|
-
|
|
91
|
-
[tool.ruff.lint]
|
|
92
|
-
extend-select = [
|
|
93
|
-
"B",
|
|
94
|
-
"I",
|
|
95
|
-
"S",
|
|
96
|
-
"UP",
|
|
97
|
-
]
|
|
98
|
-
|
|
99
|
-
[tool.ruff.lint.isort]
|
|
100
|
-
known-first-party = ["ulid_transform", "tests"]
|
|
101
|
-
|
|
102
|
-
[tool.ruff.lint.per-file-ignores]
|
|
103
|
-
"bench/*" = ["S101"]
|
|
104
|
-
"tests/*" = ["S101", "S311"]
|
|
105
|
-
|
|
106
|
-
[build-system]
|
|
107
|
-
requires = ['setuptools>=77.0', "poetry-core>=2.1.0"]
|
|
108
|
-
build-backend = "poetry.core.masonry.api"
|
ulid_transform-2.2.4/setup.py
DELETED
|
@@ -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.4',
|
|
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&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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|