From 2c2161fa753439ed953e4fa50893454e268a170a Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Sat, 15 Jun 2024 13:30:55 +0200 Subject: [PATCH] Make `uuid3` and `uuid5` compatible with Python 3.12 (#56) --- .github/workflows/ci.yml | 5 ++-- python/uuid_utils/__init__.pyi | 22 +++++++++++---- python/uuid_utils/compat/__init__.pyi | 24 ++++++++++++---- src/lib.rs | 40 ++++++++++++++++++++------- tests/test_uuid.py | 10 ++++--- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2326a4..eba5c16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,16 +31,17 @@ jobs: run: make docs_build linux: - name: "Linux: ${{ matrix.target }}" + name: "Linux: ${{ matrix.target }} Python ${{ matrix.python-version }}" runs-on: ubuntu-latest strategy: matrix: target: [x86_64, i686] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "${{ matrix.python-version }}" - name: Build wheels uses: PyO3/maturin-action@v1 with: diff --git a/python/uuid_utils/__init__.pyi b/python/uuid_utils/__init__.pyi index 4fd8f49..309a3ad 100644 --- a/python/uuid_utils/__init__.pyi +++ b/python/uuid_utils/__init__.pyi @@ -142,17 +142,27 @@ def uuid1(node: _Int | None = None, clock_seq: _Int | None = None) -> UUID: otherwise a random 14-bit sequence number is chosen.""" ... -def uuid3(namespace: UUID, name: str) -> UUID: - """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" - ... +if sys.version_info >= (3, 12): + def uuid3(namespace: UUID, name: str | bytes) -> UUID: + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" + ... +else: + def uuid3(namespace: UUID, name: str) -> UUID: + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" + ... def uuid4() -> UUID: """Generate a random UUID.""" ... -def uuid5(namespace: UUID, name: str) -> UUID: - """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" - ... +if sys.version_info >= (3, 12): + def uuid5(namespace: UUID, name: str | bytes) -> UUID: + """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" + ... +else: + def uuid5(namespace: UUID, name: str) -> UUID: + """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" + ... def uuid6( node: _Int | None = None, timestamp: _Int | None = None, nanos: _Int | None = None diff --git a/python/uuid_utils/compat/__init__.pyi b/python/uuid_utils/compat/__init__.pyi index e1e2954..6cd2f37 100644 --- a/python/uuid_utils/compat/__init__.pyi +++ b/python/uuid_utils/compat/__init__.pyi @@ -1,3 +1,4 @@ +import sys from uuid import ( NAMESPACE_DNS, NAMESPACE_OID, @@ -39,17 +40,28 @@ def uuid1(node: _Int | None = None, clock_seq: _Int | None = None) -> UUID: otherwise a random 14-bit sequence number is chosen.""" ... -def uuid3(namespace: UUID, name: str) -> UUID: - """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" - ... +if sys.version_info >= (3, 12): + def uuid3(namespace: UUID, name: str | bytes) -> UUID: + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" + ... + +else: + def uuid3(namespace: UUID, name: str) -> UUID: + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" + ... def uuid4() -> UUID: """Generate a random UUID.""" ... -def uuid5(namespace: UUID, name: str) -> UUID: - """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" - ... +if sys.version_info >= (3, 12): + def uuid5(namespace: UUID, name: str | bytes) -> UUID: + """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" + ... +else: + def uuid5(namespace: UUID, name: str) -> UUID: + """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" + ... def uuid6(node: _Int | None = None, timestamp: _Int | None = None) -> UUID: """Generate a version 6 UUID using the given timestamp and a host ID. diff --git a/src/lib.rs b/src/lib.rs index c8cebd1..124921b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,14 @@ pub const RFC_4122: &str = "specified in RFC 4122"; pub const RESERVED_MICROSOFT: &str = "reserved for Microsoft compatibility"; pub const RESERVED_FUTURE: &str = "reserved for future definition"; +#[derive(FromPyObject)] +enum StringOrBytes { + #[pyo3(transparent, annotation = "str")] + String(String), + #[pyo3(transparent, annotation = "bytes")] + Bytes(Vec), +} + #[pyclass(subclass, module = "uuid_utils")] #[derive(Clone, Debug)] struct UUID { @@ -317,10 +325,15 @@ fn uuid1(node: Option, clock_seq: Option) -> PyResult { } #[pyfunction] -fn uuid3(namespace: UUID, name: &str) -> PyResult { - Ok(UUID { - uuid: Uuid::new_v3(&namespace.uuid, name.as_bytes()), - }) +fn uuid3(namespace: UUID, name: StringOrBytes) -> PyResult { + match name { + StringOrBytes::String(name) => Ok(UUID { + uuid: Uuid::new_v3(&namespace.uuid, name.as_bytes()), + }), + StringOrBytes::Bytes(name) => Ok(UUID { + uuid: Uuid::new_v3(&namespace.uuid, &name), + }), + } } #[pyfunction] @@ -331,10 +344,15 @@ fn uuid4() -> PyResult { } #[pyfunction] -fn uuid5(namespace: &UUID, name: &str) -> PyResult { - Ok(UUID { - uuid: Uuid::new_v5(&namespace.uuid, name.as_bytes()), - }) +fn uuid5(namespace: &UUID, name: StringOrBytes) -> PyResult { + match name { + StringOrBytes::String(name) => Ok(UUID { + uuid: Uuid::new_v5(&namespace.uuid, name.as_bytes()), + }), + StringOrBytes::Bytes(name) => Ok(UUID { + uuid: Uuid::new_v5(&namespace.uuid, &name), + }), + } } #[pyfunction] @@ -347,7 +365,8 @@ fn uuid6(node: Option, timestamp: Option, nanos: Option) -> PyRes let uuid = match timestamp { Some(timestamp) => { - let timestamp = Timestamp::from_unix(&Context::new_random(), timestamp, nanos.unwrap_or(0)); + let timestamp = + Timestamp::from_unix(&Context::new_random(), timestamp, nanos.unwrap_or(0)); return Ok(UUID { uuid: Uuid::new_v6(timestamp, node), }); @@ -361,7 +380,8 @@ fn uuid6(node: Option, timestamp: Option, nanos: Option) -> PyRes fn uuid7(timestamp: Option, nanos: Option) -> PyResult { let uuid = match timestamp { Some(timestamp) => { - let timestamp = Timestamp::from_unix(&Context::new_random(), timestamp, nanos.unwrap_or(0)); + let timestamp = + Timestamp::from_unix(&Context::new_random(), timestamp, nanos.unwrap_or(0)); return Ok(UUID { uuid: Uuid::new_v7(timestamp), }); diff --git a/tests/test_uuid.py b/tests/test_uuid.py index e52e996..f023845 100644 --- a/tests/test_uuid.py +++ b/tests/test_uuid.py @@ -73,8 +73,9 @@ def test_uuid1() -> None: assert isinstance(uuid, uuid_utils.UUID) -def test_uuid3() -> None: - uuid = uuid_utils.uuid3(namespace=uuid_utils.NAMESPACE_DNS, name="python.org") +@pytest.mark.parametrize("name", ["python.org", b"python.org"]) +def test_uuid3(name: str) -> None: + uuid = uuid_utils.uuid3(namespace=uuid_utils.NAMESPACE_DNS, name=name) assert isinstance(uuid, uuid_utils.UUID) @@ -83,8 +84,9 @@ def test_uuid4() -> None: assert isinstance(uuid, uuid_utils.UUID) -def test_uuid5() -> None: - uuid = uuid_utils.uuid5(namespace=uuid_utils.NAMESPACE_DNS, name="python.org") +@pytest.mark.parametrize("name", ["python.org", b"python.org"]) +def test_uuid5(name: str) -> None: + uuid = uuid_utils.uuid5(namespace=uuid_utils.NAMESPACE_DNS, name=name) assert isinstance(uuid, uuid_utils.UUID)