picoid 0.0.1__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.
- picoid-0.0.1/LICENSE +28 -0
- picoid-0.0.1/MANIFEST.in +4 -0
- picoid-0.0.1/PKG-INFO +20 -0
- picoid-0.0.1/README.md +2 -0
- picoid-0.0.1/pyproject.toml +40 -0
- picoid-0.0.1/requirements.txt +3 -0
- picoid-0.0.1/setup.cfg +4 -0
- picoid-0.0.1/setup.py +25 -0
- picoid-0.0.1/src/picoid/__about__.py +3 -0
- picoid-0.0.1/src/picoid/__init__.pxd +2 -0
- picoid-0.0.1/src/picoid/__init__.py +12 -0
- picoid-0.0.1/src/picoid/hex.h +47 -0
- picoid-0.0.1/src/picoid/picoid.pxd +38 -0
- picoid-0.0.1/src/picoid/picoid.pyi +85 -0
- picoid-0.0.1/src/picoid/picoid.pyx +377 -0
- picoid-0.0.1/src/picoid/uuid.c +69 -0
- picoid-0.0.1/src/picoid/uuid.h +8 -0
- picoid-0.0.1/src/picoid/uuid.pxd +16 -0
- picoid-0.0.1/src/picoid.egg-info/PKG-INFO +20 -0
- picoid-0.0.1/src/picoid.egg-info/SOURCES.txt +22 -0
- picoid-0.0.1/src/picoid.egg-info/dependency_links.txt +1 -0
- picoid-0.0.1/src/picoid.egg-info/requires.txt +8 -0
- picoid-0.0.1/src/picoid.egg-info/top_level.txt +1 -0
- picoid-0.0.1/tests/test_picoid.py +31 -0
picoid-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, picops
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
picoid-0.0.1/MANIFEST.in
ADDED
picoid-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: picoid
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Low Level UUID Implementation for Python
|
|
5
|
+
Author-email: ckirua <aquipongoalgo.dsz@gmail.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: setuptools>=80.9.0
|
|
11
|
+
Requires-Dist: Cython
|
|
12
|
+
Requires-Dist: picobuild==0.0.5b1
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
15
|
+
Requires-Dist: sphinx>=7.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# picoid
|
|
20
|
+
Low Level UUID Implementation for Python
|
picoid-0.0.1/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools",
|
|
4
|
+
"wheel",
|
|
5
|
+
"Cython",
|
|
6
|
+
"picobuild==0.0.5b1"
|
|
7
|
+
]
|
|
8
|
+
build-backend = "setuptools.build_meta"
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "picoid"
|
|
12
|
+
description = "Low Level UUID Implementation for Python"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = "BSD-3-Clause"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
authors = [{ name = "ckirua", email = "aquipongoalgo.dsz@gmail.com" }]
|
|
17
|
+
requires-python = ">=3.13"
|
|
18
|
+
dynamic = ["version"]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"setuptools>=80.9.0",
|
|
21
|
+
"Cython",
|
|
22
|
+
"picobuild==0.0.5b1",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=8.0.0", "sphinx>=7.0.0", "sphinx-rtd-theme>=2.0.0"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
include-package-data = true
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.dynamic]
|
|
35
|
+
version = { attr = "picoid.__about__.__version__" }
|
|
36
|
+
|
|
37
|
+
[tool.cibuildwheel]
|
|
38
|
+
enable = ["cpython-freethreading"]
|
|
39
|
+
build = "cp313-* cp314-* cp313t-* cp314t-*"
|
|
40
|
+
build-frontend = "build[uv]"
|
picoid-0.0.1/setup.cfg
ADDED
picoid-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from picobuild import Extension, cythonize, get_cython_build_dir, setup
|
|
2
|
+
|
|
3
|
+
extensions = cythonize(
|
|
4
|
+
Extension(
|
|
5
|
+
"picoid.*",
|
|
6
|
+
["src/picoid/*.pyx", "src/picoid/uuid.c"],
|
|
7
|
+
extra_compile_args=[
|
|
8
|
+
"-O2",
|
|
9
|
+
"-march=native",
|
|
10
|
+
"-Wno-unused-function",
|
|
11
|
+
"-Wno-unused-variable",
|
|
12
|
+
],
|
|
13
|
+
language="c",
|
|
14
|
+
),
|
|
15
|
+
compiler_directives={"language_level": 3},
|
|
16
|
+
build_dir=get_cython_build_dir(),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
setup(
|
|
22
|
+
name="picoid",
|
|
23
|
+
version="0.0.1",
|
|
24
|
+
ext_modules=extensions,
|
|
25
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Low-level UUID implementation for Python.
|
|
3
|
+
|
|
4
|
+
Exposes UUID (compatible with stdlib uuid.UUID), uuid4(), and randstr_16().
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
|
|
9
|
+
from .__about__ import __version__
|
|
10
|
+
from .picoid import UUID, randstr_16, uuid4
|
|
11
|
+
|
|
12
|
+
__all__: Tuple[str, ...] = ("UUID", "__version__", "randstr_16", "uuid4")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Copyright (C) 2016-present the asyncpg authors and contributors
|
|
2
|
+
* <see AUTHORS file>
|
|
3
|
+
*
|
|
4
|
+
* This module is part of asyncpg and is released under
|
|
5
|
+
* the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#define HEX_PRELUDE const char *__hexm = "0123456789abcdef";
|
|
11
|
+
|
|
12
|
+
#define HEX_1_BYTE(buf, dest) \
|
|
13
|
+
{ \
|
|
14
|
+
char byte = (buf)[0]; \
|
|
15
|
+
(dest)[0] = __hexm[(byte >> 4) & 0x0F]; \
|
|
16
|
+
(dest)[1] = __hexm[byte & 0x0F]; \
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#define HEX_2_BYTES(buf, dest) \
|
|
20
|
+
{HEX_1_BYTE(buf, dest) HEX_1_BYTE(buf + 1, dest + 2)}
|
|
21
|
+
|
|
22
|
+
#define HEX_4_BYTES(buf, dest) \
|
|
23
|
+
{HEX_2_BYTES(buf, dest) HEX_2_BYTES(buf + 2, dest + 4)}
|
|
24
|
+
|
|
25
|
+
#define HEX_8_BYTES(buf, dest) \
|
|
26
|
+
{HEX_4_BYTES(buf, dest) HEX_4_BYTES(buf + 4, dest + 8)}
|
|
27
|
+
|
|
28
|
+
static inline void uuid_to_str(const char *source, char *dest) {
|
|
29
|
+
HEX_PRELUDE
|
|
30
|
+
|
|
31
|
+
HEX_4_BYTES(source, dest)
|
|
32
|
+
dest[8] = '-';
|
|
33
|
+
HEX_2_BYTES(source + 4, dest + 9)
|
|
34
|
+
dest[13] = '-';
|
|
35
|
+
HEX_2_BYTES(source + 6, dest + 14)
|
|
36
|
+
dest[18] = '-';
|
|
37
|
+
HEX_2_BYTES(source + 8, dest + 19)
|
|
38
|
+
dest[23] = '-';
|
|
39
|
+
HEX_4_BYTES(source + 10, dest + 24)
|
|
40
|
+
HEX_2_BYTES(source + 14, dest + 32)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static inline void uuid_to_hex(const char *source, char *dest) {
|
|
44
|
+
HEX_PRELUDE
|
|
45
|
+
HEX_8_BYTES(source, dest)
|
|
46
|
+
HEX_8_BYTES(source + 8, dest + 16)
|
|
47
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
|
|
3
|
+
cimport cpython
|
|
4
|
+
cimport cython
|
|
5
|
+
from libc.stdint cimport int8_t, uint8_t
|
|
6
|
+
from libc.string cimport memcmp, memcpy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
cdef extern from "Python.h":
|
|
10
|
+
int PyUnicode_1BYTE_KIND
|
|
11
|
+
const char* PyUnicode_AsUTF8AndSize(object unicode, Py_ssize_t *size) except NULL
|
|
12
|
+
object PyUnicode_FromKindAndData(int kind, const void *buffer, Py_ssize_t size)
|
|
13
|
+
object PyBytes_FromStringAndSize(const char *s, Py_ssize_t len)
|
|
14
|
+
|
|
15
|
+
cdef extern from "hex.h":
|
|
16
|
+
cdef void uuid_to_str(const char *source, char *dest)
|
|
17
|
+
cdef void uuid_to_hex(const char *source, char *dest)
|
|
18
|
+
|
|
19
|
+
cdef extern from "uuid.h":
|
|
20
|
+
cdef void c_uuid4(unsigned char* dest) nogil
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
cdef class __UUIDReplaceMe:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@cython.final
|
|
27
|
+
@cython.no_gc_clear
|
|
28
|
+
cdef class UUID(__UUIDReplaceMe):
|
|
29
|
+
cdef char[16] _data
|
|
30
|
+
cdef object _int
|
|
31
|
+
cdef object _hash
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
cdef UUID uuid_from_buf(const char *buf)
|
|
35
|
+
cdef void uuid_bytes_from_str(str u, char *out)
|
|
36
|
+
|
|
37
|
+
cpdef bytes randstr_16()
|
|
38
|
+
cpdef UUID uuid4()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Any, Tuple, Union
|
|
3
|
+
|
|
4
|
+
class UUID(uuid.UUID):
|
|
5
|
+
"""
|
|
6
|
+
UUID type compatible with stdlib uuid.UUID, with fast C-backed storage.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
- inp: A hyphenated UUID string (32 hex chars with optional hyphens)
|
|
10
|
+
or exactly 16 bytes.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
None (constructor).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, inp: Union[str, bytes]) -> None: ...
|
|
17
|
+
@property
|
|
18
|
+
def bytes(self) -> bytes: ...
|
|
19
|
+
@property
|
|
20
|
+
def int(self) -> int: ...
|
|
21
|
+
@property
|
|
22
|
+
def is_safe(self) -> uuid.SafeUUID: ...
|
|
23
|
+
def __str__(self) -> str: ...
|
|
24
|
+
@property
|
|
25
|
+
def hex(self) -> str: ...
|
|
26
|
+
def __repr__(self) -> str: ...
|
|
27
|
+
def __reduce__(self) -> Tuple[type, Tuple[bytes]]: ...
|
|
28
|
+
def __eq__(self, other: Any) -> bool: ...
|
|
29
|
+
def __ne__(self, other: Any) -> bool: ...
|
|
30
|
+
def __lt__(self, other: Any) -> bool: ...
|
|
31
|
+
def __gt__(self, other: Any) -> bool: ...
|
|
32
|
+
def __le__(self, other: Any) -> bool: ...
|
|
33
|
+
def __ge__(self, other: Any) -> bool: ...
|
|
34
|
+
def __hash__(self) -> int: ...
|
|
35
|
+
def __int__(self) -> int: ...
|
|
36
|
+
@property
|
|
37
|
+
def bytes_le(self) -> bytes: ...
|
|
38
|
+
@property
|
|
39
|
+
def fields(self) -> Tuple[int, int, int, int, int, int]: ...
|
|
40
|
+
@property
|
|
41
|
+
def time_low(self) -> int: ...
|
|
42
|
+
@property
|
|
43
|
+
def time_mid(self) -> int: ...
|
|
44
|
+
@property
|
|
45
|
+
def time_hi_version(self) -> int: ...
|
|
46
|
+
@property
|
|
47
|
+
def clock_seq_hi_variant(self) -> int: ...
|
|
48
|
+
@property
|
|
49
|
+
def clock_seq_low(self) -> int: ...
|
|
50
|
+
@property
|
|
51
|
+
def time(self) -> int: ...
|
|
52
|
+
@property
|
|
53
|
+
def clock_seq(self) -> int: ...
|
|
54
|
+
@property
|
|
55
|
+
def node(self) -> int: ...
|
|
56
|
+
@property
|
|
57
|
+
def urn(self) -> str: ...
|
|
58
|
+
@property
|
|
59
|
+
def variant(self) -> uuid.UUID: ...
|
|
60
|
+
@property
|
|
61
|
+
def version(self) -> int: ...
|
|
62
|
+
|
|
63
|
+
def randstr_16() -> bytes:
|
|
64
|
+
"""
|
|
65
|
+
Return 16 random bytes with UUID4 version/variant bits set.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
(none)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
16 random bytes with UUID4 version/variant bits set (bytes).
|
|
72
|
+
"""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def uuid4() -> UUID:
|
|
76
|
+
"""
|
|
77
|
+
Generate a random UUID (version 4).
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
(none)
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A new random UUID4 instance (UUID).
|
|
84
|
+
"""
|
|
85
|
+
...
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# Copyright (C) 2016-present the asyncpg authors and contributors
|
|
2
|
+
# <see AUTHORS file>
|
|
3
|
+
#
|
|
4
|
+
# This module is part of asyncpg and is released under
|
|
5
|
+
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Cython implementation of UUID, uuid4, and randstr_16.
|
|
9
|
+
|
|
10
|
+
Provides a fast UUID type compatible with stdlib uuid.UUID and helpers
|
|
11
|
+
for generating random UUIDs or 16-byte values.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import uuid
|
|
15
|
+
|
|
16
|
+
cimport cpython
|
|
17
|
+
cimport cython
|
|
18
|
+
from libc.stdint cimport int8_t, uint8_t
|
|
19
|
+
from libc.string cimport memcmp, memcpy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
cdef extern from "Python.h":
|
|
23
|
+
const char* PyUnicode_AsUTF8AndSize(
|
|
24
|
+
object unicode, Py_ssize_t *size) except NULL
|
|
25
|
+
object PyUnicode_FromKindAndData(
|
|
26
|
+
int kind, const void *buffer, Py_ssize_t size)
|
|
27
|
+
object PyBytes_FromStringAndSize(const char *s, Py_ssize_t len)
|
|
28
|
+
|
|
29
|
+
cdef extern from "hex.h":
|
|
30
|
+
cdef void uuid_to_str(const char *source, char *dest)
|
|
31
|
+
cdef void uuid_to_hex(const char *source, char *dest)
|
|
32
|
+
|
|
33
|
+
cdef extern from "uuid.h":
|
|
34
|
+
void c_uuid4(unsigned char* dest) nogil
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
cdef char[256] _hextable
|
|
38
|
+
_hextable[:] = [
|
|
39
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
40
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
41
|
+
-1,-1, 0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,
|
|
42
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
43
|
+
-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
44
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
45
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
46
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
47
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
48
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
49
|
+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
cdef std_UUID = uuid.UUID
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
cdef void uuid_bytes_from_str(str u, char *out):
|
|
57
|
+
cdef:
|
|
58
|
+
const char *orig_buf
|
|
59
|
+
Py_ssize_t size
|
|
60
|
+
unsigned char ch
|
|
61
|
+
uint8_t acc, part, acc_set
|
|
62
|
+
int i, j
|
|
63
|
+
|
|
64
|
+
orig_buf = PyUnicode_AsUTF8AndSize(u, &size)
|
|
65
|
+
if size > 36 or size < 32:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f'invalid UUID {u!r}: '
|
|
68
|
+
f'length must be between 32..36 characters, got {size}')
|
|
69
|
+
|
|
70
|
+
acc_set = 0
|
|
71
|
+
j = 0
|
|
72
|
+
for i in range(size):
|
|
73
|
+
ch = <unsigned char>orig_buf[i]
|
|
74
|
+
if ch == <unsigned char>b'-':
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
part = <uint8_t><int8_t>_hextable[ch]
|
|
78
|
+
if part == <uint8_t>-1:
|
|
79
|
+
if ch >= 0x20 and ch <= 0x7e:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f'invalid UUID {u!r}: unexpected character {chr(ch)!r}')
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError('invalid UUID {u!r}: unexpected character')
|
|
84
|
+
|
|
85
|
+
if acc_set:
|
|
86
|
+
acc |= part
|
|
87
|
+
out[j] = <char>acc
|
|
88
|
+
acc_set = 0
|
|
89
|
+
j += 1
|
|
90
|
+
else:
|
|
91
|
+
acc = <uint8_t>(part << 4)
|
|
92
|
+
acc_set = 1
|
|
93
|
+
|
|
94
|
+
if j > 16 or (j == 16 and acc_set):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f'invalid UUID {u!r}: decodes to more than 16 bytes')
|
|
97
|
+
|
|
98
|
+
if j != 16:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f'invalid UUID {u!r}: decodes to less than 16 bytes')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
cdef class __UUIDReplaceMe:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
cdef UUID uuid_from_buf(const char *buf):
|
|
108
|
+
cdef:
|
|
109
|
+
UUID u = UUID.__new__(UUID)
|
|
110
|
+
memcpy(u._data, buf, 16)
|
|
111
|
+
return u
|
|
112
|
+
|
|
113
|
+
@cython.final
|
|
114
|
+
@cython.no_gc_clear
|
|
115
|
+
cdef class UUID(__UUIDReplaceMe):
|
|
116
|
+
"""
|
|
117
|
+
UUID type compatible with stdlib uuid.UUID, with fast C-backed storage.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
- inp: A hyphenated UUID string (32 hex chars with optional hyphens)
|
|
121
|
+
or exactly 16 bytes.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
None (constructor).
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __cinit__(self):
|
|
128
|
+
self._int = None
|
|
129
|
+
self._hash = None
|
|
130
|
+
|
|
131
|
+
def __init__(self, inp):
|
|
132
|
+
cdef:
|
|
133
|
+
char *buf
|
|
134
|
+
Py_ssize_t size
|
|
135
|
+
|
|
136
|
+
if cpython.PyBytes_Check(inp):
|
|
137
|
+
cpython.PyBytes_AsStringAndSize(inp, &buf, &size)
|
|
138
|
+
if size != 16:
|
|
139
|
+
raise ValueError(f'16 bytes were expected, got {size}')
|
|
140
|
+
memcpy(self._data, buf, 16)
|
|
141
|
+
|
|
142
|
+
elif cpython.PyUnicode_Check(inp):
|
|
143
|
+
uuid_bytes_from_str(inp, self._data)
|
|
144
|
+
else:
|
|
145
|
+
raise TypeError(f'a bytes or str object expected, got {inp!r}')
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def bytes(self):
|
|
149
|
+
return cpython.PyBytes_FromStringAndSize(self._data, 16)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def int(self):
|
|
153
|
+
if self._int is None:
|
|
154
|
+
# The cache is important because `self.int` can be
|
|
155
|
+
# used multiple times by __hash__ etc.
|
|
156
|
+
#
|
|
157
|
+
# The or 0 works around a bug interaction between cpython
|
|
158
|
+
# 3.10 and earlier and Cython ~3.0.11 in which
|
|
159
|
+
# int.from_bytes returns a "non-canonical 0" and then
|
|
160
|
+
# # Cython's implementation of & mishandles it.
|
|
161
|
+
# # See cython/cython#6480.
|
|
162
|
+
self._int = int.from_bytes(self.bytes, 'big') or 0
|
|
163
|
+
return self._int
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_safe(self):
|
|
167
|
+
return uuid.SafeUUID.unknown
|
|
168
|
+
|
|
169
|
+
def __str__(self):
|
|
170
|
+
cdef char[36] out
|
|
171
|
+
uuid_to_str(self._data, out)
|
|
172
|
+
return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, <void*>out, 36)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def hex(self):
|
|
176
|
+
cdef char[32] out
|
|
177
|
+
uuid_to_hex(self._data, out)
|
|
178
|
+
return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, <void*>out, 32)
|
|
179
|
+
|
|
180
|
+
def __repr__(self):
|
|
181
|
+
return f"UUID('{self}')"
|
|
182
|
+
|
|
183
|
+
def __reduce__(self):
|
|
184
|
+
return (type(self), (self.bytes,))
|
|
185
|
+
|
|
186
|
+
def __eq__(self, other):
|
|
187
|
+
if type(other) is UUID:
|
|
188
|
+
return memcmp(self._data, (<UUID>other)._data, 16) == 0
|
|
189
|
+
if isinstance(other, std_UUID):
|
|
190
|
+
return self.int == other.int
|
|
191
|
+
return NotImplemented
|
|
192
|
+
|
|
193
|
+
def __ne__(self, other):
|
|
194
|
+
if type(other) is UUID:
|
|
195
|
+
return memcmp(self._data, (<UUID>other)._data, 16) != 0
|
|
196
|
+
if isinstance(other, std_UUID):
|
|
197
|
+
return self.int != other.int
|
|
198
|
+
return NotImplemented
|
|
199
|
+
|
|
200
|
+
def __lt__(self, other):
|
|
201
|
+
if type(other) is UUID:
|
|
202
|
+
return memcmp(self._data, (<UUID>other)._data, 16) < 0
|
|
203
|
+
if isinstance(other, std_UUID):
|
|
204
|
+
return self.int < other.int
|
|
205
|
+
return NotImplemented
|
|
206
|
+
|
|
207
|
+
def __gt__(self, other):
|
|
208
|
+
if type(other) is UUID:
|
|
209
|
+
return memcmp(self._data, (<UUID>other)._data, 16) > 0
|
|
210
|
+
if isinstance(other, std_UUID):
|
|
211
|
+
return self.int > other.int
|
|
212
|
+
return NotImplemented
|
|
213
|
+
|
|
214
|
+
def __le__(self, other):
|
|
215
|
+
if type(other) is UUID:
|
|
216
|
+
return memcmp(self._data, (<UUID>other)._data, 16) <= 0
|
|
217
|
+
if isinstance(other, std_UUID):
|
|
218
|
+
return self.int <= other.int
|
|
219
|
+
return NotImplemented
|
|
220
|
+
|
|
221
|
+
def __ge__(self, other):
|
|
222
|
+
if type(other) is UUID:
|
|
223
|
+
return memcmp(self._data, (<UUID>other)._data, 16) >= 0
|
|
224
|
+
if isinstance(other, std_UUID):
|
|
225
|
+
return self.int >= other.int
|
|
226
|
+
return NotImplemented
|
|
227
|
+
|
|
228
|
+
def __hash__(self):
|
|
229
|
+
# In EdgeDB every schema object has a uuid and there are
|
|
230
|
+
# huge hash-maps of them. We want UUID.__hash__ to be
|
|
231
|
+
# as fast as possible.
|
|
232
|
+
if self._hash is not None:
|
|
233
|
+
return self._hash
|
|
234
|
+
|
|
235
|
+
self._hash = hash(self.int)
|
|
236
|
+
return self._hash
|
|
237
|
+
|
|
238
|
+
def __int__(self):
|
|
239
|
+
return self.int
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def bytes_le(self):
|
|
243
|
+
bytes = self.bytes
|
|
244
|
+
return (bytes[4-1::-1] + bytes[6-1:4-1:-1] + bytes[8-1:6-1:-1] +
|
|
245
|
+
bytes[8:])
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def fields(self):
|
|
249
|
+
return (self.time_low, self.time_mid, self.time_hi_version,
|
|
250
|
+
self.clock_seq_hi_variant, self.clock_seq_low, self.node)
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def time_low(self):
|
|
254
|
+
return self.int >> 96
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def time_mid(self):
|
|
258
|
+
return (self.int >> 80) & 0xffff
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def time_hi_version(self):
|
|
262
|
+
return (self.int >> 64) & 0xffff
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def clock_seq_hi_variant(self):
|
|
266
|
+
return (self.int >> 56) & 0xff
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def clock_seq_low(self):
|
|
270
|
+
return (self.int >> 48) & 0xff
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def time(self):
|
|
274
|
+
return (((self.time_hi_version & 0x0fff) << 48) |
|
|
275
|
+
(self.time_mid << 32) | self.time_low)
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def clock_seq(self):
|
|
279
|
+
return (((self.clock_seq_hi_variant & 0x3f) << 8) |
|
|
280
|
+
self.clock_seq_low)
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def node(self):
|
|
284
|
+
return self.int & 0xffffffffffff
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def urn(self):
|
|
288
|
+
return 'urn:uuid:' + str(self)
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def variant(self):
|
|
292
|
+
if not self.int & (0x8000 << 48):
|
|
293
|
+
return uuid.RESERVED_NCS
|
|
294
|
+
elif not self.int & (0x4000 << 48):
|
|
295
|
+
return uuid.RFC_4122
|
|
296
|
+
elif not self.int & (0x2000 << 48):
|
|
297
|
+
return uuid.RESERVED_MICROSOFT
|
|
298
|
+
else:
|
|
299
|
+
return uuid.RESERVED_FUTURE
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def version(self):
|
|
303
|
+
# The version bits are only meaningful for RFC 4122 UUIDs.
|
|
304
|
+
if self.variant == uuid.RFC_4122:
|
|
305
|
+
return int((self.int >> 76) & 0xf)
|
|
306
|
+
|
|
307
|
+
# <hack>
|
|
308
|
+
# In order for `isinstance(pgproto.UUID, uuid.UUID)` to work,
|
|
309
|
+
# patch __bases__ and __mro__ by injecting `uuid.UUID`.
|
|
310
|
+
#
|
|
311
|
+
# We apply brute-force here because the following pattern stopped
|
|
312
|
+
# working with Python 3.8:
|
|
313
|
+
#
|
|
314
|
+
# cdef class OurUUID:
|
|
315
|
+
# ...
|
|
316
|
+
#
|
|
317
|
+
# class UUID(OurUUID, uuid.UUID):
|
|
318
|
+
# ...
|
|
319
|
+
#
|
|
320
|
+
# With Python 3.8 it now produces
|
|
321
|
+
#
|
|
322
|
+
# "TypeError: multiple bases have instance lay-out conflict"
|
|
323
|
+
#
|
|
324
|
+
# error. Maybe it's possible to fix this some other way, but
|
|
325
|
+
# the best solution possible would be to just contribute our
|
|
326
|
+
# faster UUID to the standard library and not have this problem
|
|
327
|
+
# at all. For now this hack is pretty safe and should be
|
|
328
|
+
# compatible with future Pythons for long enough.
|
|
329
|
+
#
|
|
330
|
+
assert UUID.__bases__[0] is __UUIDReplaceMe
|
|
331
|
+
assert UUID.__mro__[1] is __UUIDReplaceMe
|
|
332
|
+
cpython.Py_INCREF(std_UUID)
|
|
333
|
+
cpython.PyTuple_SET_ITEM(UUID.__bases__, 0, std_UUID)
|
|
334
|
+
cpython.Py_INCREF(std_UUID)
|
|
335
|
+
cpython.PyTuple_SET_ITEM(UUID.__mro__, 1, std_UUID)
|
|
336
|
+
# </hack>
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
cdef pg_UUID = UUID
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
cpdef bytes randstr_16():
|
|
344
|
+
"""
|
|
345
|
+
Return 16 random bytes with UUID4 version/variant bits set.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
(none)
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
16 random bytes with UUID4 version/variant bits set (bytes).
|
|
352
|
+
"""
|
|
353
|
+
cdef:
|
|
354
|
+
unsigned char dest[16]
|
|
355
|
+
bytes result
|
|
356
|
+
with nogil:
|
|
357
|
+
c_uuid4(dest)
|
|
358
|
+
result = PyBytes_FromStringAndSize(<char*>dest, 16)
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
cpdef UUID uuid4():
|
|
362
|
+
"""
|
|
363
|
+
Generate a random UUID (version 4).
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
(none)
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
A new random UUID4 instance (UUID).
|
|
370
|
+
"""
|
|
371
|
+
cdef:
|
|
372
|
+
unsigned char dest[16]
|
|
373
|
+
UUID result
|
|
374
|
+
with nogil:
|
|
375
|
+
c_uuid4(dest)
|
|
376
|
+
result = uuid_from_buf(<const char *>dest)
|
|
377
|
+
return result
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#include <stdint.h>
|
|
2
|
+
#include <string.h>
|
|
3
|
+
#include <stdatomic.h>
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
|
|
6
|
+
#ifdef _WIN32
|
|
7
|
+
#include <windows.h>
|
|
8
|
+
#include <wincrypt.h>
|
|
9
|
+
static HCRYPTPROV hProvider = 0;
|
|
10
|
+
#else
|
|
11
|
+
#include <fcntl.h>
|
|
12
|
+
#include <unistd.h>
|
|
13
|
+
#include <errno.h>
|
|
14
|
+
static int urandom_fd = -1;
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
#define UUID_SIZE 16
|
|
18
|
+
#define BUFFER_SIZE 8192 // 8KB buffer (512 UUIDs)
|
|
19
|
+
static unsigned char random_buffer[BUFFER_SIZE];
|
|
20
|
+
static _Atomic size_t buffer_pos = BUFFER_SIZE; // Start empty to force initial fill
|
|
21
|
+
|
|
22
|
+
static void fill_buffer(void) {
|
|
23
|
+
#ifdef _WIN32
|
|
24
|
+
CryptGenRandom(hProvider, BUFFER_SIZE, random_buffer);
|
|
25
|
+
#else
|
|
26
|
+
if (urandom_fd != -1) {
|
|
27
|
+
ssize_t bytes_read = read(urandom_fd, random_buffer, BUFFER_SIZE);
|
|
28
|
+
if (bytes_read != BUFFER_SIZE) {
|
|
29
|
+
// Handle error - could not read enough random bytes
|
|
30
|
+
exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
#endif
|
|
34
|
+
buffer_pos = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static void __attribute__((constructor)) init_rng(void) {
|
|
38
|
+
#ifdef _WIN32
|
|
39
|
+
CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
|
|
40
|
+
#else
|
|
41
|
+
urandom_fd = open("/dev/urandom", O_RDONLY);
|
|
42
|
+
#endif
|
|
43
|
+
fill_buffer(); // Initial fill
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static void __attribute__((destructor)) cleanup_rng(void) {
|
|
47
|
+
#ifdef _WIN32
|
|
48
|
+
if (hProvider) CryptReleaseContext(hProvider, 0);
|
|
49
|
+
#else
|
|
50
|
+
if (urandom_fd != -1) close(urandom_fd);
|
|
51
|
+
#endif
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void c_uuid4(uint8_t uuid[UUID_SIZE]) {
|
|
55
|
+
size_t current_pos = atomic_fetch_add(&buffer_pos, UUID_SIZE);
|
|
56
|
+
|
|
57
|
+
// Check if we need to refill
|
|
58
|
+
if (current_pos + UUID_SIZE > BUFFER_SIZE) {
|
|
59
|
+
fill_buffer();
|
|
60
|
+
current_pos = atomic_fetch_add(&buffer_pos, UUID_SIZE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Copy bytes from buffer
|
|
64
|
+
memcpy(uuid, random_buffer + current_pos, UUID_SIZE);
|
|
65
|
+
|
|
66
|
+
// Set version and variant
|
|
67
|
+
uuid[6] = (uuid[6] & 0x0F) | 0x40;
|
|
68
|
+
uuid[8] = (uuid[8] & 0x3F) | 0x80;
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
cdef extern from "uuid.h":
|
|
2
|
+
cdef void c_uuid4(unsigned char* uuid)
|
|
3
|
+
|
|
4
|
+
################################################################################
|
|
5
|
+
# Copyright (C) 2016-present the asyncpg authors and contributors
|
|
6
|
+
# <see AUTHORS file>
|
|
7
|
+
#
|
|
8
|
+
# This module is part of asyncpg and is released under
|
|
9
|
+
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
cdef extern from "hex.h":
|
|
12
|
+
cdef void uuid_to_str(const char *source, char *dest)
|
|
13
|
+
cdef void uuid_to_hex(const char *source, char *dest)
|
|
14
|
+
# cdef void uuid_to_int(const char *source, unsigned long long *dest)
|
|
15
|
+
|
|
16
|
+
################################################################################
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: picoid
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Low Level UUID Implementation for Python
|
|
5
|
+
Author-email: ckirua <aquipongoalgo.dsz@gmail.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: setuptools>=80.9.0
|
|
11
|
+
Requires-Dist: Cython
|
|
12
|
+
Requires-Dist: picobuild==0.0.5b1
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
15
|
+
Requires-Dist: sphinx>=7.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# picoid
|
|
20
|
+
Low Level UUID Implementation for Python
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
requirements.txt
|
|
6
|
+
setup.py
|
|
7
|
+
src/picoid/__about__.py
|
|
8
|
+
src/picoid/__init__.pxd
|
|
9
|
+
src/picoid/__init__.py
|
|
10
|
+
src/picoid/hex.h
|
|
11
|
+
src/picoid/picoid.pxd
|
|
12
|
+
src/picoid/picoid.pyi
|
|
13
|
+
src/picoid/picoid.pyx
|
|
14
|
+
src/picoid/uuid.c
|
|
15
|
+
src/picoid/uuid.h
|
|
16
|
+
src/picoid/uuid.pxd
|
|
17
|
+
src/picoid.egg-info/PKG-INFO
|
|
18
|
+
src/picoid.egg-info/SOURCES.txt
|
|
19
|
+
src/picoid.egg-info/dependency_links.txt
|
|
20
|
+
src/picoid.egg-info/requires.txt
|
|
21
|
+
src/picoid.egg-info/top_level.txt
|
|
22
|
+
tests/test_picoid.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
picoid
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Minimal pytest tests for picoid: every exported name works as expected."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import picoid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_all_exported_names_are_available() -> None:
|
|
9
|
+
"""Every name in __all__ is importable from picoid and is usable."""
|
|
10
|
+
assert hasattr(picoid, "__all__")
|
|
11
|
+
assert isinstance(picoid.__all__, tuple)
|
|
12
|
+
assert all(isinstance(name, str) for name in picoid.__all__)
|
|
13
|
+
for name in picoid.__all__:
|
|
14
|
+
assert hasattr(
|
|
15
|
+
picoid, name
|
|
16
|
+
), f"picoid.__all__ contains {name!r} but picoid has no attribute {name!r}"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_version() -> None:
|
|
20
|
+
"""__version__ is a non-empty string."""
|
|
21
|
+
assert hasattr(picoid, "__version__")
|
|
22
|
+
v = picoid.__version__
|
|
23
|
+
assert isinstance(v, str)
|
|
24
|
+
assert len(v) > 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_import_from_picoid() -> None:
|
|
28
|
+
"""All __all__ names can be imported via 'from picoid import ...'."""
|
|
29
|
+
for name in picoid.__all__:
|
|
30
|
+
module = __import__("picoid", fromlist=[name])
|
|
31
|
+
assert hasattr(module, name), f"from picoid import {name!r} failed"
|